临时对象
- 产生原因:隐式类型转换与函数返回值
- 临时对象一般在出语句后就会被析构,但是如果使用
const T& t引用了,则生存周期与引用t一样,如下p4
const T&可以绑定到所有右值和左值,只能读取并能延长生存周期- 右值分为
纯右值和临时量,当const int& c = 20;等价于int temp = 20;const int &c = temp;//temp为临时量
- C++规定临时对象天生是const的,不能被修改,即为右值
- 编译器优化,当使用临时变量初始化一个新对象时,不会生成临时变量,如下t4,等价于
static Test t4(30,30);,同样如果是使用函数返回值来初始化一个新对象,依然不会生成临时对象
//补充注意事项
t2 = (Test)(50,50); //后面是逗号表达式,返回最后的表达式50,所以等价于t2 = Test(50)
对象传递优化
- 初始代码(无优化)
- 函数参数传递是初始化
- 优化
- 函数参数使用引用传递,
Test GetObject(Test &t) - 返回时直接返回临时变量,
return Test(val) - 接受函数返回使用新对象(初始化),
Test t2 = GetObject(t1)
- 返回临时变量时,对于临时变量而言就是用临时对象构造一个新对象,所以在被调函数中不会进行构造,只会在临时变量中进行构造
- 而对于接受变量而言,又是使用临时变量构造一个一个新对象,所以临时变量直接被优化而不产生
- 当然对于汇编而言,是将新对象
t2的地址也压入了栈帧,让GetObject直接在t2内存上进行构造
- 移动拷贝构造/移动赋值操作,
String(String &&str)
当被调函数中必须使用有名对象返回(即左值)时,此时构造函数无法避免,希望能窃取到有名对象内的资源,比如string中的char*开辟的内存空间,而不是单独开辟空间后一个个拷贝
- 如下所示,对于tmpStr会被优化成右值(将亡值),临时变量中调用移动拷贝构造,将内部分配的堆空间给临时变量,然后对于str2也是调用右值赋值操作传入临时对象,将其堆内存资源窃取
//特别说明
要将传入参数原本指向堆内存的指针赋为nullptr,因为会调用析构函数释放
str.mptr = nullptr;
- 移动函数形参是右值引用,但是右值引用变量是左值,所以如果想在函数中继续调用移动函数,必须继续使用
std::move()
std::move()就是将变量强制转换成右值
- 完美转发std::forward<T>()是保持变量左值右值,但是必须搭配万能引用
T&&
万能引用
template\<typename T> void wrapper(T&& arg) { // arg是万能引用 // 必须是`T&&`形式(直接`T`或`const T&&`都不是) }引用折叠:
- 左值引用主导:只要出现一个
&(左值引用),结果必定是T&。具体包括:T& &→T&T& &&→T&T&& &→T&- 纯右值引用:仅当所有引用均为
&&时,结果才是T&&:T&& &&→T&&- 模板中使用万能引用时,左值实参推导为T&,右值或普通变量推导为T,然后将T传入forward()中进行实例化,调用后返回相应的强制转换后的变量
template<typename T>
T&& forward(remove_reference_t<T>& arg) {
//这里remove_reference_t是去掉引用,使得形参为引用
return static_cast<T&&>(arg);
}
//这里返回了引用,其实就是返回了别名(指针),不会开辟临时变量
void moveTest(string&& s)
{
// s是右值引用,但是s在函数中是变量,具名左值
// print(s);//会调用左值函数
print(std::move(s));// 此才会调用移动函数
}
moveTest("hello");//字面量就是右值
template<typename T>
void forwardTest(T&& s)
{
// 完美转发搭配着模板使用
// T& &、T& &&、T&& & 都会折叠成左值引用 T&
// T&& && 折叠成右值引用 T&&
// s此时可能是左值,也可能是右值,因为T类型不确定
print(std::forward<T>(s));保持s传入时的类型
}
forwardTest("world");//此时函数内s为右值
s="!"
forwardTest(s);//此时函数内s为左值