5. CPP对象优化

97 阅读4分钟

临时对象

  1. 产生原因:隐式类型转换函数返回值
  2. 临时对象一般在出语句后就会被析构,但是如果使用const T& t引用了,则生存周期与引用t一样,如下p4
  • const T& 可以绑定到所有右值和左值,只能读取并能延长生存周期
  • 右值分为纯右值临时量,当const int& c = 20;等价于int temp = 20;const int &c = temp;//temp为临时量
  1. C++规定临时对象天生是const的,不能被修改,即为右值
  2. 编译器优化,当使用临时变量初始化一个新对象时,不会生成临时变量,如下t4,等价于static Test t4(30,30);,同样如果是使用函数返回值来初始化一个新对象,依然不会生成临时对象

image.png

//补充注意事项
t2 = (Test)(50,50); //后面是逗号表达式,返回最后的表达式50,所以等价于t2 = Test(50)

对象传递优化

  1. 初始代码(无优化)
  • 函数参数传递是初始化 image.png
  1. 优化
  • 函数参数使用引用传递,Test GetObject(Test &t)
  • 返回时直接返回临时变量,return Test(val)
  • 接受函数返回使用新对象(初始化),Test t2 = GetObject(t1)
  • 返回临时变量时,对于临时变量而言就是用临时对象构造一个新对象,所以在被调函数中不会进行构造,只会在临时变量中进行构造
  • 而对于接受变量而言,又是使用临时变量构造一个一个新对象,所以临时变量直接被优化而不产生
  • 当然对于汇编而言,是将新对象t2的地址也压入了栈帧,让GetObject直接在t2内存上进行构造

image.png

  • 移动拷贝构造/移动赋值操作,String(String &&str)

当被调函数中必须使用有名对象返回(即左值)时,此时构造函数无法避免,希望能窃取到有名对象内的资源,比如string中的char*开辟的内存空间,而不是单独开辟空间后一个个拷贝

  • 如下所示,对于tmpStr会被优化成右值(将亡值),临时变量中调用移动拷贝构造,将内部分配的堆空间给临时变量,然后对于str2也是调用右值赋值操作传入临时对象,将其堆内存资源窃取 image.png

image.png

//特别说明
要将传入参数原本指向堆内存的指针赋为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为左值