仅为个人理解,个人笔记
部分代码和内容引用于文章从4行代码看右值引用
代码(来自链接文章里)
#include <iostream>
using namespace std;
int g_constructCount=0;
int g_copyConstructCount=0;
int g_destructCount=0;
struct A
{
A(){
cout<<"construct: "<<++g_constructCount<<endl;
}
A(const A& a)
{
cout<<"copy construct: "<<++g_copyConstructCount <<endl;
}
~A()
{
cout<<"destruct: "<<++g_destructCount<<endl;
}
};
A GetA()
{
return A();
}
int main() {
A a = GetA();
return 0;
}
加上-fno-elide-constructors 输出结果
construct: 1
copy construct: 1
destruct: 1
copy construct: 2
destruct: 2
destruct: 3
解释
第一次构造是在GetA()函数栈里调用A()而生成的,此时对象保存在GetA()函数栈里
第一次拷贝构造是在GetA()函数栈里完成的,目的是为了将对象返回到main()函数栈里,从GetA()函数返回到main()函数栈里的对象称为<临时对象>
第一次析构是在GetA()函数栈里完成的,是在GetA()函数返回之前的最后一步操作
然后,恢复main()函数栈的上下文,并且将从GetA()函数返回的值也放在main()函数栈中
接下来第二次拷贝构造是将main()函数栈里从GetA()函数返回的对象拷贝到变量a中,此时变量a是一个对象,且在main()函数栈中
第二次析构函数是将main()函数栈里从GetA()函数返回的对象出栈,此时<临时对象>的生命结束
接下来是main函数结束
第三次析构函数是因为main函数结束,且a对象不是返回值,因此不用调用拷贝构造函数生成main函数的临时返回值对象
采用右值引用
代码(来自链接文章里)
int main() {
A&& a = GetA();
return 0;
}
加上-fno-elide-constructors 输出结果
construct: 1
copy construct: 1
destruct: 1
destruct: 2
解释
因为在main函数里采用了右值引用,此时a变量不再是一个对象,而是一个引用(引用本质上是一个指针常量),且a存放的地址是临时对象的地址,且临时对象不会出栈,因此临时对象也不会析构,在main函数结束时才会统一出栈,因此a变量和临时变量的生命周期一样,是main()函数栈的生命周期
采用右值引用后相对没有采用右值引用,提高了时间效率(取消了临时对象的拷贝和析构),但在mian()函数栈里要多消耗一小点空间,这个空间根据系统不同而不同,例如32位系统就会在main()函数栈里多消耗32bit
移动构造函数与右值
移动构造函数举例
classname(classname&& rightVlue){
...
}
只有在赋值语句(如=号)右边的值是右值时才会使用这个移动构造函数,例如函数的返回值,或者应用移动语义std::move将左值对象转换成一个右值对象
作用:因为拷贝构造函数一定要进行深拷贝(避免类内的指针漂移),因此在对临时对象进行拷贝时也要进行深拷贝,这样增加了不必要的开销,因为调用拷贝构造函数进行深拷贝是为了保护被拷贝的对象,但是临时对象在拷贝构造函数调用完后,就直接析构了(如果不使用右值引用的话),所以直接写一个移动构造函数,当要拷贝的对象是临时对象(右值)的时候,直接移动对象而不是去拷贝对象(例如临时对象成员p指向一块堆内存,那么移动构造函数不是再像拷贝构函数那样malloc一块堆内存被拷贝,而是直接继承临时对象的这一块堆内存,然后将临时对象的p置空,这样在析构时也会不删除这块堆内存),这样就减小了不必要的开销
移动语义std::move
作用:将一个对象转换成右值(不管对象原来是左值还是右值)
直接上代码
template<typename T>
void print(T&& t){
cout << "rvalue" << endl;
}
template<typename T>
void print(T& t){
cout << "lvalue" << endl;
}
int main(){
int x = 1 ;
print(std::move(1));
print(std::move(x));
}
输出:
rvalue
rvalue
完美转发std::forward
作用 : 用在模板函数中,可以按照参数原来的类型进行转发
不使用完美转发
template<typename T>
void print(T&& t){
cout << "rvalue" << endl;
}
template<typename T>
void print(T& t){
cout << "lvalue" << endl;
}
template <typename T>
void foo(T&& x){
print(x);
}
int main(){
int x = 1 ;
foo(1);
foo(x);
}
输出 :
lvalue
lvalue
使用完美转发
template<typename T>
void print(T&& t){
cout << "rvalue" << endl;
}
template<typename T>
void print(T& t){
cout << "lvalue" << endl;
}
template <typename T>
void foo(T&& x){
print(std::forward<T>(x));
}
int main(){
int x = 1 ;
foo(1);
foo(x);
}
输出
rvalue
lvalue