C++右值引用和移动构造函数
2021/10/23 17:12:44
本文主要是介绍C++右值引用和移动构造函数,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
对象的拷贝
C++新标准之前对象的拷贝控制由拷贝构造函数,重载的拷贝赋值运算符,析构函数三个函数决定。
新标准之后新增两个函数:移动构造函数,移动赋值运算符
左值和右值
左值(lvalue) 指持久存在的对象或返回值类型为左值引用的返回值,是不可移动的。
右值(rvalue) 包含了临时对象或者返回值为右值引用的返回值,是可移动的。
- 形式为cast-name
(exp),其中type是转换的类型,exp是转换的值,如果type是引用类型,则结果是左值 - 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值
- 内置的解引用运算符、下标运算符、迭代器解引用运算符、容器的下标运算符的求值结果都是左值
- 内置类型和迭代器的递增递减运算符作用于左值对象,所得的结果也是左值
右值引用
为了支持移动操作,C++新标准引入了一种新的引用类型——右值引用&&
,即必须绑定到右值的引用
int &&i = 42; //正确 int j = 42; int &&k = j; //错误,右值引用不能绑定到左值 const int &r = j*42; //正确,const会创建一个临时的const变量 int &r1 = j*42; //错误,右值引用不能绑定到左值 int &&r2 = j*42; //正确,j*42是一个右值 int &&r3 = i; //错误,表达式i是一个左值
返回左值引用的函数,连同赋值、下标、解引用和前置递增/递减运算符都是返回左值表达式的例子。
返回非引用类型的函数,连同算术、关系、位以及后置递增/递减运算符,都生成右值,不能将左值引用绑定到这类表达式,但可以将一个const的左值引用或者一个右值引用绑定到这类表达式上。
强制转换右值move函数
有时候我们需要将左值像右值一样转移所有权
void func(){ A res; //..... if(xxx) ans = res; //我希望将res转移到外部变量ans上 return; }
在上述代码中,res赋值给ans之后不再被使用,我们希望调用的是移动赋值构造函数。
但是res是一个左值,因此ans = res调用的是赋值构造函数。
为了将某些左值当成右值使用,C++新标准提供了 std::move 函数以用于将某些左值转成右值,以匹配右值引用类型。
void func(){ A res; //..... if(xxx) ans = std::move(res); //这时候调用的是移动赋值构造函数 return; }
函数参数传递
void func1(A a) {return;} void func2(A &&a) {return;} int main() { A a; A &b = a; A c; A d; //请回答:不开优化的版本下,调用以下函数分别有多少Copy Consturct、Move Construct的开销? func1(a); //调用拷贝构造函数 func1(b); //调用拷贝构造函数 func1(std::move(c)); //调用移动构造函数 func2(std::move(d)); //都不调用 }
实际上在不开优化的版本下,如果实参为右值,调用func1的开销只比func2多了一次移动构造函数和析构函数。
实参传递给形参,即形参会根据实参来构造。其结果是调用了移动构造函数;函数结束时则释放形参。
倘若说对象的移动构造函数开销较低(例如内部仅一个指针属性),那么使用无引用类型的形参函数是更优雅的选择,而且还能接受左值引用类型或无引用的实参(尽管这两种实参都会导致一次Copy Consturct)。可以说,这种情况下,只提供非引用类型的版本,也是可以接受的。
从极致的优化角度来看,如果参数有支持移动构造(或移动赋值)的类型,应该同时提供左值引用(匹配左值)和右值引用(匹配右值)两种重载版本。
函数返还值传递
A func1() { A a; return a; } A func2() { A a; return std::move(a); } A &&func3() { A a; return std::move(a); } int main() { A test1 = func1(); //test1调用情况 Construct func A test2 = func2(); //test2调用情况 Construct func Move func Destroy func A test3 = func3(); //test3调用情况 Construct func Destroy func Move func return 0; }
执行这3行代码实际上都没有任何Copy Construct的开销,并且func3是危险的。因为局部变量释放后,函数返还值仍持有它的右值引用。
因此,不建议函数返还右值引用类型,同前面传递参数类似的,移动构造开销不大的时候,直接返还非引用类型就足够了(在某些特殊场合有特别作用,准确来说一般用于表示返还成一个右值,如std::move的实现)。
结论:
1. 我们应该首先把编写右值引用类型相关的任务重点放在对象的构造、赋值函数上。从源头上出发,在编写其它代码时会自然而然享受到了移动构造、移动赋值的优化效果。
2. 形参:从优化的角度上看,若参数有支持移动构造(或移动赋值)的类型,应提供左值引用和右值引用的重载版本。移动开销很低时,只提供一个非引用类型的版本也是可以接受的。
3. 返还值:不要且没必要编写返还右值引用类型的函数,除非有特殊用途。
参考:
1. <<C++ Primer>>第五版
2. 透彻理解C++11 移动语义:右值、右值引用std::move、std::forward
这篇关于C++右值引用和移动构造函数的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04el-table 开启定时器下,表格的选中状态会消失是什么原因-icode9专业技术文章分享
- 2024-10-03如何安装和初始化飞牛私有云 fnOS?-icode9专业技术文章分享
- 2024-10-03如何安装 App 并连接到飞牛 NAS?-icode9专业技术文章分享
- 2024-10-03如何安装飞牛 TV 并连接到影视服务器?-icode9专业技术文章分享
- 2024-10-03如何在PVE和ESXI上安装飞牛私有云 fnOS?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS安装系统异常情况处理-icode9专业技术文章分享
- 2024-10-03飞牛NAS如何创建存储空间?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS硬盘会自动休眠吗?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS如何安装飞牛影视和创建媒体库?-icode9专业技术文章分享
- 2024-10-03fnOS国产最强NAS如何为家人朋友开通影视账号?-icode9专业技术文章分享