ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++右值引用和移动构造函数

2021-10-23 15:34:02  阅读:138  来源: 互联网

标签:函数 右值 左值 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++,引用,移动,构造函数
来源: https://www.cnblogs.com/WiatrY/p/15443246.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有