一、对象的构造和析构
#include "iostream"
class Test {
public:
Test(int data = 0) : ma(data) { std::cout << "default construction" << std::endl; }
Test(const Test &t) : ma(t.ma) { std::cout << "copy construction" << std::endl; }
Test &operator=(const Test &t) {
ma = t.ma;
return *this;
std::cout << "operator=" << std::endl;
}
private:
int ma;
};
int main() {
Test t1;
Test t2(t1);
Test t3 = t1;
//Test(20)的是临时对象,临时对象的生存周期就是当前表达式
//未优化是,按照逻辑会先调用默认构造函数,然后再调用拷贝构造函数
//C++编译器对对象构造的优化:用临时对象构造新对象的时候,临时对象不产生,直接构造新对象(对象优化的原理)
Test t4 = Test(20);
//显式生成临时对象
//以下语句,赋值的时候临时对象是会产生的,临时对象产生后再调用赋值运算符重载
t4 = Test(40);
//类型强转,编译器会使用带int参数的构造函数进行转换
t4 = (Test) 40;
//隐式生成临时对象
t4 = 40;
//以下语句不合法,因为出了语句,临时对象就不存在了,不能被赋值给指针
Test *p = &Test(40);
//可以用常引用指向临时对象
const Test &ref = Test(50);
system("pause");
return 0;
}
对象的各种构造函数:
- 默认构造函数;
- 带左值引用参数的拷贝构造函数;
- 带右值引用参数的拷贝构造函数。
分析对象构造注意事项:
- 区分赋值和对象初始化,赋值调用的是operator=,初始化调用的拷贝构造函数;
- 区分带左值引用的拷贝构造函数和带右值引用的拷贝构造函数,由于右值本身也是左值,此时需要使用forward;
- 区分函数调用和对象构造的区别。
TestClass a()
,这是函数的定义,TestClass a=TestClass()
,这是对象的构造。
二、对象优化规则和原理分析
原理分析:
使用值传递给函数传参,需要使用实参给形参进行初始化,相当于形参对象的拷贝构造,这个过程会构造对象。函数返回返回值时,由于不能直接把返回值保存下来(函数运行结束,返回值所在栈内存就被回收了),会先使用返回值构造一个临时对象,然后用临时对象进行赋值或者构造。
优化规则:
- 函数参数传递过程中,对象优先按照引用传递,这样可以减少参数传递过程中对象的构造;
- 当函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象;
- 接受返回值是对象的函数调用的时候,优先按照初始化的方式接受,不要按赋值的方式接收。
三、左值和右值,左值引用和右值引用
- 左值:有内存,有名字,且可以明确的可识别的内存位置;
- 右值:无内存(如立即数存在于寄存器中)或者无名字,内存不可明确识别(比如临时变量);
- 左值引用&:只能引用左值;
- 右值引用&&:只能引用右值,本质还是产生了临时变量,然后再引用。一个右值引用变量本身上也是一个左值,不能用右值变量再引用。
//int tmp=20;
//const int &a = tmp;
//以下语句本质上是产生了临时变量如上
const int &a = 20;
//int tmp=20;
//int &b = tmp;
//以下语句本质上是产生了临时变量如上
int &&b = 20;
四、对拷贝构造函数和赋值重载的修改
由于使用临时对象给某个对象赋值或者构造的时候,临时对象可能会开辟资源,赋值给对象的之后又要被释放,但是临时对象开辟的资源和被赋值的对象是相同的,我们可以直接让被赋值的对象指向临时对象开辟的资源,避免多次的开辟和释放相同内容的资源。
class Test{
public:
Test(const Test &t){}
//增加带右值引用参数的拷贝构造函数
Test(const Test &&t){}
Test& operator=(const Test &t){}
//增加带右值引用参数的赋值重载函数
Test& operator=(const Test &&t){}
};
五、move和forward语义
- move
int &&a = 10;
std::move(a); //将左值转换成右值
- 模板和引用折叠相结合
利用模板的实参推演
template<typename T>
void test(T &&val) {
cout << val << endl;
cout << __FUNCTION__ << endl;
}
int main() {
int a = 100;
test(a); //引用折叠,& + && = &,此时test函数中的val就是左值
test(10); //&& + && = &&,此时test函数中的val就是右值
system("pause");
return 0;
}
- forward
std::forward<T>(val) //类型完美转发,能够识别左值和右值