C++ 右值引用

block://6984617523950616580?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38

右值的引入

作为在C++11中引入的一个类型,容易引起误解的是,右值引用并没有说明引入是为了什么,是为了解决什么问题。

右值引用可以解决以下问题

  1. 实现移动语义
  2. 完美转发

左值和右值来自原先的C语言,左值可以出现在赋值左边或者右边,而右值只能出现在赋值的右边

int a = 42;
int b = 43;// a and b are both l-values:
a = b; // ok
b = a; // ok
a = a * b; // ok// a * b is an rvalue:
int c = a * b; // ok, rvalue on right hand side of assignment
a * b = 42; // error, rvalue on left hand side of assignment

在 C++ 中,这作为第一个直观的左值和右值方法仍然很有用。但是,带有用户定义类型的 C++ 引入了一些关于可修改性和可分配性的微妙之处,导致此定义不正确。我们没有必要进一步讨论这个问题。这是一个替代定义,尽管它仍然存在争议,但它将使您能够处理右值引用:左值是一个引用内存位置的表达式,并允许我们通过&操作符取得地址,右值,不是左值的都是右值。

// lvalues:
//
int i = 42;
i = 43; // ok, i is an lvalue
int* p = &i; // ok, i is an lvalue
int& foo();
foo() = 42; // ok, foo() is an lvalue
int* p1 = &foo(); // ok, foo() is an lvalue// rvalues:
//
int foobar();
int j = 0;
j = foobar(); // ok, foobar() is an rvalue
int* p2 = &foobar(); // error, cannot take the address of an rvalue
j = 42; // ok, 42 is an rvalue

移动语义

假设有一个类X,类中的成员变量m_pResource是一个需要花费时间和内存取进行构造和析构的类型,比如m_pResource是一个vector类型,对其进行赋值时将会产生大量的析构和构造函数的调用。

X& X::operator=(X const & rhs)
{// [...]// Make a clone of what rhs.m_pResource refers to.// Destruct the resource that m_pResource refers to. // Attach the clone to m_pResource.// [...]
}

同样的问题会出现在copy构造函数上

X foo();
X x;
// perhaps use x in various ways
x = foo();
  • clones the resource from the temporary returned by foo,

  • destructs the resource held by x and replaces it with the clone,

  • destructs the temporary and thereby releases its resource.

当赋值操作符的右边是右值的话,只是交换值的指针是比较高效的

// [...]
// swap m_pResource and rhs.m_pResource
// [...]

上述这种操作就是移动语义,可以通过操作符重载实现

X& X::operator=(<mystery type> rhs)
{// [...]// swap this->m_pResource and rhs.m_pResource// [...]
}

以上调用无论是赋值还是copy构造函数,都会导致大量的构造函数和析构函数调用(如当vector中存储很多的类对象时),因此我们当然希望能够实现对传入类型的引用,从而避免这些构造函数和析构函数的调用

block://6984620384730546178?from=docs_block&id=ce31003ceb5efb1f7a7c0a5fbe6cb60191627a38

右值引用

如果X是一个类型,那么X&& 就是对X类型的右值引用,为了更好的区分X&被称为左值引用

一个右值引用类型很多地方表现与左值引用相同,除了一些例外。最重要的一条就是,当进行函数重载的时候,左值当成参数传入函数,偏向调用左值引用的函数;当右值传入函数时,更加偏向调用右值重载的函数

void foo(X& x); // 左值函数重载
void foo(X&& x); // 右值函数重载X x;
X foobar();foo(x); // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)

Rvalue references allow a function to branch at compile time (via overload resolution) on the condition “Am I being called on an lvalue or an rvalue?”

大体意思就是,右值引用允许编译器期间通过是右值还是左值调用不同的函数

当然你可以使用上述方法重载任何函数,就像上述所示。但是通常会被用于重载拷贝构造函数和赋值构造函数,用来实现移动语义

X& X::operator=(X const & rhs); // classical implementationX& X::operator=(X&& rhs)
{// Move semantics: exchange content between this and rhsreturn *this;
}
Note: If you implement
void foo(X&);
but not
void foo(X&&);
then of course the behavior is unchanged: foo can be called on l-values, but not on r-values. If you implement
void foo(X const &);
but not
void foo(X&&);
then again, the behavior is unchanged: foo can be called on l-values and r-values, but it is not possible to make it distinguish between l-values and r-values. That is possible only by implementing
void foo(X&&);
as well. Finally, if you implement
void foo(X&&);
but neither one of
void foo(X&);
and
void foo(X const &);
then, according to the final version of C++11, foo can be called on r-values, but trying to call it on an l-value will trigger a compile error.

强制移动语义

我们都知道,在给予更多控制权和避免粗心大意犯错方面C++选择给予更多的控制权,你不但可以在右值上实现移动语义,而且你可以自行决定在左值上实现移动语义,一个很好的例子就是std::swap函数

template<class T>
void swap(T& a, T& b)
{ T tmp(a);a = b; b = tmp;
} X a, b;
swap(a, b);

这里没有使用右值,因此有没有实现移动语义,但是我们知道实现移动语义会更好,只要变量作为复制构造或者赋值的源出现,该变量要么根本就不再使用,要么就作为赋值的目标。

C++11中与一个被调用的库函数std::move可以将其参数转换 右值, 不做其他事情

void swap(T& a, T& b)
{ T tmp(std::move(a));a = std::move(b); b = std::move(tmp);
} X a, b;
swap(a, b);

修改之后上述三行实现了移动语义,需要注意的是,对于那些没有实现移动语义的类型(即:没有使用右值引用版本重载它们的复制构造函数和赋值运算符),对于这些类型新的swap就和旧的一样

既然、知道了移动语义std::move,如下:

a = b;

你期望在这里发生什么?你期望a持有的对象被b的复制出来的副本替换,并且希望a先前持有的对象析构,现在我们考虑一下语义:

a = std::move(b);

如果实现了移动语义,会交换a和b持有的对象,不会有任何对象进行析构。当然结束之后a原先持有的对象的生命周期将和b的作用范围绑定,b超出范围a原先持有的对象将会被销毁。

所以从某种意义上说,我们在这里陷入了非确定性破坏的阴暗世界:一个变量已被分配,但该变量以前持有的对象仍在某处。只要该对象的销毁不会产生任何外界可见的副作用,就可以了。但有时析构函数确实有这样的副作用。一个例子是释放析构函数内的锁。因此,具有副作用的对象销毁的任何部分都应该在复制赋值运算符的右值引用重载中显式执行:

X& X::operator=(X&& rhs)
{// Perform a cleanup that takes care of at least those parts of the// destructor that have side effects. Be sure to leave the object// in a destructible and assignable state.// Move semantics: exchange content between this and rhsreturn *this;
}

右值引用就是右值吗?

像以前一样,我们为X实现复制构造函数和赋值操作符重载来实现移动语义。

假如:

void foo(X&& x)
{X anotherX = x; //  调用右值引用赋值重载函数还是左值???// ...
}

代码中函数内x是一个左值引用,然而我们期望让右值引用就是本身就是右值。右值引用的设计者提供了一个更好的思路:

Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.

大意就是,右值引用可以是左值也可以是右值,评判的标准是,如果这个值有命名就是左值,如果没有就是右值。

那么上述代码中,虽然参数传进的是右值,但是进入函数的时候,因为x已经有命名了,所以函数内部的x是左值,那么函数内部调用的也是左值的赋值函数

void foo(X&& x)
{X anotherX = x; // calls X(X const & rhs)
}

如下是一个没有名字的右值,因此会调用右值赋值函数

X&& goo();
X x = goo(); // calls X(X&& rhs) because the thing on// the right hand side has no name

这种设计的背后思路就是:允许移动语义应用于一些有名字的对象

X anotherX = x;// x is still in scope!

以上语句是非常危险的,移动的食物应该在移动后立即死亡并消失,因此有一条规则,如果它有一个名字,那么它就是左值

全文链接:右值引用

一文读懂什么是C++移动语义《一》相关推荐

  1. 从实验室走向大众,一文读懂Nanopore测序技术的发展及应用

    关键词/Nanopore测序技术    文/基因慧 随着基因测序技术不断突破,二代测序的发展也将基因检测成本大幅降低.理想的测序方法,是对原始DNA模板进行直接.准确的测序,消除PCR扩增带来的偏差, ...

  2. 一文读懂Faster RCNN

    来源:信息网络工程研究中心本文约7500字,建议阅读10+分钟 本文从四个切入点为你介绍Faster R-CNN网络. 经过R-CNN和Fast RCNN的积淀,Ross B. Girshick在20 ...

  3. 福利 | 一文读懂系列文章精选集发布啦!

    大数据时代已经悄然到来,越来越多的人希望学习一定的数据思维和技能来武装自己,虽然各种介绍大数据技术的文章每天都扑面而来,但纷繁又零散的知识常常让我们不知该从何入手:同时,为了感谢和回馈读者朋友对数据派 ...

  4. ​一文读懂EfficientDet

    一文读懂EfficientDet. 今年年初Google Brain团队在 CVPR 2020 上发布了 EfficientDet目标检测模型, EfficientDet是一系列可扩展的高效的目标检测 ...

  5. 一文读懂序列建模(deeplearning.ai)之序列模型与注意力机制

    https://www.toutiao.com/a6663809864260649485/ 作者:Pulkit Sharma,2019年1月21日 翻译:陈之炎 校对:丁楠雅 本文约11000字,建议 ...

  6. AI洞观 | 一文读懂英特尔的AI之路

    AI洞观 | 一文读懂英特尔的AI之路 https://mp.weixin.qq.com/s/E9NqeywzQ4H2XCFFOFcKXw 11月13日-14日,英特尔人工智能大会(AIDC)在北京召 ...

  7. 一文读懂机器学习中的模型偏差

    一文读懂机器学习中的模型偏差 http://blog.sina.com.cn/s/blog_cfa68e330102yz2c.html 在人工智能(AI)和机器学习(ML)领域,将预测模型参与决策过程 ...

  8. 一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现

    一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现 导读:近日,马云.马化腾.李彦宏等互联网大佬纷纷亮相2018世界人工智能大会,并登台演讲.关于人工智能的现状与未来,他们提出了各自的观点,也引 ...

  9. 一文读懂你该了解的5G知识:现在别买5G手机

    来源: 腾讯科技 2019年是中国全力布局5G的一年:三大运营商纷纷搭建基站,手机厂商发布5G手机,部分城市已经开启了5G测试--在电信日这天,腾讯科技联合知乎推出重磅策划,聚焦和5G相关的小知识,精 ...

最新文章

  1. MQTT消息长度限制
  2. Open Source的一些网站,自己收集来的
  3. spring boot实现软删除
  4. 阿里技术协会(ATA)11月系列精选文集
  5. 将Windows 8.1 系统窗口背景设置成淡绿色?
  6. 关于TobjectList的一点疑问
  7. SGU 160.Magic Multiplying Machine
  8. 麟龙指标通达信指标公式源码_通达信指标公式源码波段极限副图源码
  9. googlehelper手机版ios_nba2K17中文手机版下载_nba2K17官方中文IOS手机版V1.07下载
  10. JS — 数组去重(4种方法)
  11. 软件观念革命:交互设计精髓_电子沙盘设计主要分为哪几种?
  12. [词根词缀]cre/cred/crit/cult字根由来及词源C的故事
  13. perl中CPAN的安装
  14. 互联网医疗泡沫破灭,一场从线上回归线下的技术圈地运动?
  15. Openwrt switch vlan配置
  16. 关于网站标签页的logo设置
  17. Android 内存溢出、垃圾回收、四种引用
  18. 什么是 Kubernetes HPA
  19. Pipeline流水线-通过Jenkinsfile构建任务
  20. IntelliJ IDEA 快捷键大全

热门文章

  1. UBOOT 2011-3版本分析(初步感受)
  2. one trick pony
  3. 返回结果集的存储过程实例及调用
  4. NYOJ 586 疯牛
  5. Golang之funcval结构体
  6. [Tips]Torch功能点记录
  7. 977 AlvinZH过生日(背包DP大作战S)
  8. JSP连接数据库 - MySQL
  9. 发个上海英雄会聚会沙龙的公告 希望和大家一起见面交流探讨
  10. 2006第三季度:10大最糟科技事件