【C/C++】【类和对象】临时对象

临时对象

C++中真正的临时对象是不可见的,在源代码中不会出现,且不在堆上分配内存(在栈中),没有名字的对象;

//i++ ++i
//i++ 会产生一个临时对象,用来记录i的值;
int i = 1;
int &&r = i++; //i和r没关系 

产生临时对象的情况

临时对象可能发生于如下的三种情况,我们需要了解这些临时对象如何被产生和被销毁;以及如何避免产生临时对象,避免不必要的调用构造和析构;进一步提升程序的性能;

  1. 传值方式给函数传参
  2. 隐式类型转换
  3. 函数返回对象

传值方式给函数传参

  • 按照值传递的方式给函数传参,函数会产生一个实参的副本,在函数中所有的操作都是针对这个副本的,也正是因为这个原因,对副本的修改并不会影响实参的值;
  • 通过采用引用传值的方式,不用创建实参的副本,少调用一次拷贝构造和一次析构函数,同时可以通过形参来修改实参的值;
#include <iostream>
using namespace std;
class CTempValue
{
public:
	int m_val;
	int m_v;
public:
	CTempValue(int i = 0, int j = 0);
	CTempValue(const CTempValue& t) :m_val(t.m_val), m_v(t.m_v)
	{
		cout << "执行拷贝构造函数" << endl;
	}
	virtual ~CTempValue()
	{
		cout << "执行析构函数" << endl;
	}

	int add(CTempValue tmp); //普通函数
};
CTempValue::CTempValue(int i, int j):m_val(i), m_v(j)
{
	cout << "执行构造函数" << endl;
	cout << "m_val = " << m_val << endl;
	cout << "m_v = " << m_v << endl;
}

int CTempValue::add(CTempValue tmp)
{
	int tmp_val = tmp.m_val + tmp.m_v;
	tmp.m_val = 1000;
	return tmp_val;
}

int main()
{
	CTempValue tmp(10, 20); //构造函数执行
	int Sum = tmp.add(tmp); //拷贝构造函数和析构函数会执行
	//add的形式参数会拷贝实参 使用拷贝构造函数   
	//形参的释放 调用析构函数

	cout << "Sum = " << Sum << endl;
	cout << "tmp.m_val = " << tmp.m_val << endl;
}

通过引用传参解决


#include <iostream>
using namespace std;


class CTempValue
{
public:
	int m_val;
	int m_v;
public:
	CTempValue(int i = 0, int j = 0);
	CTempValue(const CTempValue& t) :m_val(t.m_val), m_v(t.m_v)
	{
		cout << "执行拷贝构造函数" << endl;
	}
	virtual ~CTempValue()
	{
		cout << "执行析构函数" << endl;
	}

	int add(CTempValue& tmp); //普通函数
};
CTempValue::CTempValue(int i, int j):m_val(i), m_v(j)
{
	cout << "执行构造函数" << endl;
	cout << "m_val = " << m_val << endl;
	cout << "m_v = " << m_v << endl;
}

int CTempValue::add(CTempValue& tmp)
{
	int tmp_val = tmp.m_val + tmp.m_v;
	tmp.m_val = 1000;
	return tmp_val;
}


int main()
{
	CTempValue tmp(10, 20); //构造函数执行
	int Sum = tmp.add(tmp); //拷贝构造函数和析构函数不会执行
	cout << "Sum = " << Sum << endl;
	cout << "tmp.m_val = " << tmp.m_val << endl;
}

类型转换

  • 将值赋给某个变量的时候(传递对象给一个参数),其类型和它即将绑定上去的参数类型不同会发生隐式类型转化;

示例1
将一个整型值赋值给一个CTempValue对象,会发生隐式类型转换;

  1. 会产生一个临时对象;
  2. 会调用拷贝赋值运算符把这个临时对象里面的各个成员赋值给sum对象
  3. 销毁这个临时创建的CTempValue对象
#include <iostream>
using namespace std;
class CTempValue
{
public:
	int m_val;
	int m_v;
public:
	CTempValue(int i = 0, int j = 0);
	CTempValue(const CTempValue& t) :m_val(t.m_val), m_v(t.m_v)
	{
		cout << "执行拷贝构造函数" << endl;
	}
	//拷贝赋值运算符
	CTempValue& operator=(const CTempValue& tmp)
	{
		m_val = tmp.m_val;
		m_v = tmp.m_v;
		cout << "执行拷贝赋值运算符" << endl;
		return *this;
	}
	virtual ~CTempValue()
	{
		cout << "执行析构函数" << endl;
	}

	int add(CTempValue& tmp); //普通函数
};
CTempValue::CTempValue(int i, int j):m_val(i), m_v(j)
{
	cout << "执行构造函数" << endl;
	cout << "m_val = " << m_val << endl;
	cout << "m_v = " << m_v << endl;
}

int CTempValue::add(CTempValue& tmp)
{
	int tmp_val = tmp.m_val + tmp.m_v;
	tmp.m_val = 1000;
	return tmp_val;
}
int main()
{
	CTempValue sum;
	sum = 1000; //会产生一个临时对象; 调用一次构造函数一次析构函数 
}

解决方法

  • 通过定义时初始化对象来达到不生成临时对象的目的;

  • =是定义时初始化;系统为对象预留空间 用1000构造对象,而且是直接构造在对象预留空间中;

    
    #include <iostream>
    using namespace std;
    class CTempValue
    {
    public:
    	int m_val;
    	int m_v;
    public:
    	CTempValue(int i = 0, int j = 0);
    	CTempValue(const CTempValue& t) :m_val(t.m_val), m_v(t.m_v)
    	{
    		cout << "执行拷贝构造函数" << endl;
    	}
    
    	//拷贝赋值运算符
    	CTempValue& operator=(const CTempValue& tmp)
    	{
    		m_val = tmp.m_val;
    		m_v = tmp.m_v;
    		cout << "执行拷贝赋值运算符" << endl;
    		return *this;
    	}
    	virtual ~CTempValue()
    	{
    		cout << "执行析构函数" << endl;
    	}
    
    	int add(CTempValue& tmp); //普通函数
    };
    CTempValue::CTempValue(int i, int j):m_val(i), m_v(j)
    {
    	cout << "执行构造函数" << endl;
    	cout << "m_val = " << m_val << endl;
    	cout << "m_v = " << m_v << endl;
    }
    int CTempValue::add(CTempValue& tmp)
    {
    	int tmp_val = tmp.m_val + tmp.m_v;
    	tmp.m_val = 1000;
    	return tmp_val;
    }
    int main()
    {
          /*	
          CTempValue sum;
    	sum = 1000;
          */ //会产生一个临时对象; 调用一次构造函数一次析构函数 一次拷贝赋值运算符
    	//1. 用1000创建CTempValue的临时对象
    	//2. 调用拷贝赋值运算符把这个临时对象里面的各个成员赋值给sum对象
    	//3. 销毁这个临时创建的CTempValue对象
    
    	CTempValue sum = 1000; //没有生成临时对象  =是定义时初始化;系统为sum预留空间  用1000构造sum对象,而且是直接构造在sum对象预留空间中;
    
    }
    

示例2

#include <iostream>
#include <string.h>
#include <vector>


using namespace std;

class MyString
{
private:
  char* m_data;
  int m_size;
public:
  //确实存在隐式类型转换,如果加上explicit代码报错
  //explicit  MyString(const char* str = nullptr)
  MyString(const char* str = nullptr)
  {
      cout << "MyString(const char* str = nullptr)" << endl;
      if(str == nullptr)
      {
          m_data = new char[1];
          m_data[0] = '\0';
          m_size = 0;
      }
      else
      {
          m_size = strlen(str);
          m_data = new char[m_size + 1];
          strcpy(m_data, str);
      }
  }

  ~MyString()
  {
      cout << "~MyString()" << endl;
      delete[] m_data;
  }

  MyString(const MyString& str)
  {
      cout << "MyString(const MyString& str)" << endl;
      m_size = str.m_size;
      m_data = new char[m_size + 1];
      strcpy(m_data, str.m_data);
  }

  MyString& operator=(const MyString& str)
  {
      cout << "MyString& operator=(const MyString& str)" << endl;
      if(this == &str) return *this;

      delete[] m_data;
      m_size = str.m_size;
      m_data = new char[m_size + 1];
      strcpy(m_data, str.m_data);
      return *this;
  }

  MyString(MyString &&str)
  {
      cout << "MyString(MyString &&str)" << endl;
      m_size = str.m_size;
      m_data = str.m_data;
      str.m_data = nullptr;
  }

  MyString& operator=(MyString &&str)
  {
      if(this == &str) return *this;
      cout << "MyString& operator=(MyString &&str)" << endl;
      delete[] m_data;
      m_size = str.m_size;
      m_data = str.m_data;
      str.m_data = nullptr;
      return *this;
  }

  char* get_data() const
  {
      return m_data;
  }

  friend ostream&operator<< (ostream& os, const MyString& str);
  friend istream&operator>> (istream& is, MyString& str);

  MyString operator+(const MyString& str)
  {
      int len = m_size + str.m_size;
      MyString res;
      delete[] res.m_data;

      res.m_size = len;
      res.m_data = new char[len + 1];
      memset(res.m_data, 0, len + 1);
      strcat(res.m_data, m_data);
      strcat(res.m_data, str.m_data);

      return res;
  }

};

ostream&operator<< (ostream& os, const MyString& str)
{
  os << str.m_data;
  return os;
}

istream&operator>> (istream& is, MyString& str)
{
  delete[] str.m_data;
  char buf[1024];
  scanf("%s", buf);
  int len = strlen(buf);
  str.m_data = new char[len + 1];
  strcpy(str.m_data, buf);
  return is;
}



MyString get_Str(MyString& str)
{
  return MyString(str.get_data());
}
//统计字符ch在str中出现的次数
//将str绑定到string临时对象上  去掉const报错  系统不允许修改临时对象
int total(const MyString& str, char ch)
{
  const char* p = str.get_data();
  int cnt = 0;
  return cnt;
}

int main()
{
  char mystr[100] = "hello world";
  int res = total(mystr, 'o');//隐式类型转化 会产生临时对象MyString 确实存在隐式类型转换,如果加上explicit代码报错
  //c++只会为const引用产生临时对象,而不会为非const引用产生临时对象???
  cout << res << endl;
  return 0;
}


优化

函数返回对象

当函数需要返回一个对象,它会在栈中创建一个临时对象,存储函数的返回值。

范例1

#include <iostream>
using namespace std;
class CTempValue
{
public:
	int m_val;
	int m_v;
public:
	CTempValue(int i = 0, int j = 0);
	CTempValue(const CTempValue& t) :m_val(t.m_val), m_v(t.m_v)
	{
		cout << "执行拷贝构造函数" << endl;
	}
	//拷贝赋值运算符
	CTempValue& operator=(const CTempValue& tmp)
	{
		m_val = tmp.m_val;
		m_v = tmp.m_v;
		cout << "执行拷贝赋值运算符" << endl;
		return *this;
	}
	virtual ~CTempValue()
	{
		cout << "执行析构函数" << endl;
	}
	int add(CTempValue& tmp); //普通函数

};
CTempValue::CTempValue(int i, int j):m_val(i), m_v(j)
{
	cout << "执行构造函数" << endl;
	cout << "m_val = " << m_val << endl;
	cout << "m_v = " << m_v << endl;
}

int CTempValue::add(CTempValue& tmp)
{
	int tmp_val = tmp.m_val + tmp.m_v;
	tmp.m_val = 1000;
	return tmp_val;
}

CTempValue Double(CTempValue& tmp)
{
	CTempValue tmp_value; //构造 + 析构
	tmp_value.m_v = tmp.m_v * 2;
	tmp_value.m_val = tmp.m_val * 2;
	return tmp_value; //产生临时对象  调用拷贝构造函数和析构函数
}

int main()
{
	CTempValue x(10, 20); //构造 + 析构
	//Double(x);
	CTempValue&& y = Double(x); //临时对象是一种右值
	CTempValue z = Double(x);

}

优化


#include <iostream>
using namespace std;


class CTempValue
{
public:
	int m_val;
	int m_v;
public:
	CTempValue(int i = 0, int j = 0);
	CTempValue(const CTempValue& t) :m_val(t.m_val), m_v(t.m_v)
	{
		cout << "执行拷贝构造函数" << endl;
	}

	//拷贝赋值运算符
	CTempValue& operator=(const CTempValue& tmp)
	{
		m_val = tmp.m_val;
		m_v = tmp.m_v;
		cout << "执行拷贝赋值运算符" << endl;
		return *this;
	}

	virtual ~CTempValue()
	{
		cout << "执行析构函数" << endl;
	}

	int add(CTempValue& tmp); //普通函数

};
CTempValue::CTempValue(int i, int j):m_val(i), m_v(j)
{
	cout << "执行构造函数" << endl;
	cout << "m_val = " << m_val << endl;
	cout << "m_v = " << m_v << endl;
}

int CTempValue::add(CTempValue& tmp)
{
	int tmp_val = tmp.m_val + tmp.m_v;
	tmp.m_val = 1000;
	return tmp_val;
}

CTempValue Double(CTempValue& tmp)
{
	return CTempValue(tmp.m_val * 2, tmp.m_v * 2);
}
int main()
{
	CTempValue x(10, 20); //构造 + 析构
	//Double(x);

	CTempValue&& y = Double(x); //临时对象是一种右值
	//CTempValue z = Double(x);

}

范例2:类外运算符重载

class mynum
{
public:
	int x;
	int y;
};
mynum operator+(mynum& num1, mynum& num2)
{
	mynum res;
	res.x = num1.x + num2.x;
	res.y = num1.y + num2.y;
	return res;
}
int main()
{
	mynum num1;
	num1.x = 1;
	num1.y = 2;


	mynum num2;
	num2.x = 3;
	num2.y = 4;

	mynum num3 = num1 + num2;

}

优化


class mynum
{
public:
	int x;
	int y;
public:
	mynum(int x = 0, int y = 0) :x(x), y(y) {};
};
mynum operator+(mynum& num1, mynum& num2)
{
	return mynum(num1.x + num2.x, num1.y + num2.y);
}
int main()
{
	mynum num1;
	num1.x = 1;
	num1.y = 2;


	mynum num2;
	num2.x = 3;
	num2.y = 4;

	mynum num3 = num1 + num2;

}

总结

为了避免产生临时对象,提高效率:

  • 函数调用传递对象时,函数形参应该按引用来接收。
  • 函数返回对象时,应该直接返回临时对象,不要先定义再返回
  • 调用返回对象的函数时,应该以初始化的形式调用(定义的同时初始化),而不是先定义对象再赋值。

除此之外还可以使用内联函数来优化函数调用;

参考:《More Effective C++》条款19(p98)

posted @ 2020-07-21 08:33  NaughtyCoder  阅读(274)  评论(0)    收藏  举报