C++ 11的右值引用
C++11 引入了 std::move 语义、右值引用、移动构造和完美转发这些特性。由于这部分篇幅比较长,分为3篇来进行阐述。
在了解这些特性之前,我们先来引入一些问题。
一、问题导入
- 函数返回值是传值的时候发生几次对象构造、几次拷贝?
- 函数的形参是值传递的时候发生几次对象构造?
让我们先来看一段代码,
// main.cpp
#include <iostream>
using namespace std;
class A{
public:
A(){
cout<<"class A construct!"<<endl;
}
A(const A&){
cout<<"class A copy!"<<endl;
}
A& operator=(const A&){
cout<<"assignment called!"<<endl;
}
~A(){
cout<<"class A destruct!"<<endl;
}
};
A get_A_value(){
return A();
}
int main(){
A a = get_A_value();
return 0;
}
使用 g++ 编译;注意使用 -fno-elide-constructors
关闭省略构造优化
g++ main.cpp -fno-elide-constructors
可以得到以下输出
class A construct!
class A destruct!
class A copy!
class A destruct!
class A destruct!
可以看到A a=get_A_value();
一行代码居然产生1次对象构造和2次对象的拷贝构造!具体为
- 在 get_A_value() 里 A() 构造了临时对象,发生了一次构造;
- 函数返回的时候会把临时对象拷贝后作为返回值,发生一次拷贝;
- A a = 函数返回值发生了拷贝构造。
如果使用编译器优化(默认), 则会把临时对象拷贝的那次 和 用返回值构造最终对象的拷贝的给省略了;也即,只有一次拷贝和析构。
class A construct!
class A destruct!
如果把上面代码改一下
// ... A
void pass_A_by_value(A a){
}
int main(){
A a;
pass_A_by_value(a);
return 0;
}
在去掉优化 g++ main.cpp -fno-elide-constructors
时输出为
class A construct!
class A copy!
class A destruct!
class A destruct!
1次构造加上1次拷贝。
因此,下次如果面试的时候有人问这个问题,你就可以说:默认情况下经过编译器优化后临时对象的拷贝就会被省去,如果使用 -fno-elide-constructors
省略优化,则还要考虑临时对象的拷贝。
事实上,在未经优化的情况下,以下时候拷贝构造函数会被调用:
- 函数内的局部对象做为返回值返回(不是引用)的时候会发生拷贝(拷贝为临时对象返回)
- 函数形参为传值的时候,会发生拷贝构造
- 一个对象以另外一个对象进行初始化的时候
对象的频繁构造是程序的开销,特别是当对象内部有堆上内存(比如有 new 出来的成员)的时候,每次拷贝构造的时候都需要用 new 申请一块内存,造成性能的降低。对于情况2,好习惯是如果函数参数是只读的(也即不会在程序内进行修改),传引用作为参数,也即 pass_A_by_refrence(const A &a)
; 对于情况1,编译器会为我们进行优化; 对于情况3,C++11 引入了一种移动构造函数的概念,它将获取**右值引用*,右值的“资源” move 到新对象中,这个过程中不会申请新的内存,从而达到提高了效率和性能。
所以,要理解些关键词 “移动构造”、“移动语义” ,首先要理解右值和右值引用。
二、右值和右值引用
2.1 左值(lvalue)和右值(rvalue)
在 一、问题导入 里我们提到了临时对象,也即函数返回值的时候只会“临时”存在的对象(运行超过那一行就会结束它的生存期),这个临时返回值就是一个右值;
右值的最直观的定义为,顾名思义:
位于赋值运算符 = 右边的值,为右值;在左边的则为左值
如
A a = foo(); // foo() 为右值
char *x = "thu"; // “thu”为字面值也为右值
a = b + c; // b + c这个结果也是一个右值
在C++中,还有个定义为:
左值可以取得地址、有名字; 不可以取得地址、没有名字的为右值。
所以 A a = foo()
可以用 &a
取得a的地址,a 是左值,然是不能取得 foo()的地址,(&foo())无法通过编译, foo()返回的临时对象也是没有名字的,所以是右值。
在C++11中,右值包括两种,一中是将亡值(xvalue, eXpiring Value),一种是纯右值(prvalue,Pure Rvalue)[1]。函数非引用返回的临时对象、运算表达式的结果、1, 3.14,'c'这样的字面值等都属于纯右值。而xvalue则是由 C++11引入的 如返回值为 A&& 的函数返回值或者std::move()的返回值等。
不深究的话,我们只需要知道左值和右值的区别就行了。对于右值的详细分类则不必深究。
2.2 左值引用和右值引用
- 左值引用就是一般的引用,一般用一个&表示,例如
const A &a_ref = a; // 取得对象 a 的引用
左值引用相当于别名,指向一个具体的对象。
- 右值引用
右值引用顾名思义,就是右值的引用, 用 &&表示;
A &&r_ref = getRvalue(); // r_ref 是一个右值引用
右值引用也相当于别名,与左值的区别为右值引用是无名变量的别名。
getRvalue() 是一个返回右值的函数,右值在这一句执行完就该结束他的生存期了,如果是对象就该调用析构函数了;但是右值引用让它强行续命;使用右值引用指向右值,右值的生存期和右值引用一样长了,这也就少一次对象的析构和构造了。
C++的右值引用主要有两个用处,一个是移动语义,一个是完美转发。这个将在接下来的两篇来讲。
总结
为了导入右值和移动语义,首先复习了以下临时对象在函数返回值和传参数时构造了几次;然后对比介绍了左值和右值,以及右值引用的形式和含义。为移动语义和完美转发的介绍做铺垫。
参考资料
- Michale Wang| IBM XL 编译器中国 《深入理解C++11》, 机械工业出版社
C++ 11的右值引用的更多相关文章
- C++11之右值引用(二):右值引用与移动语义
上节我们提出了右值引用,可以用来区分右值,那么这有什么用处? 问题来源 我们先看一个C++中被人诟病已久的问题: 我把某文件的内容读取到vector中,用函数如何封装? 大部分人的做法是: v ...
- c++11的右值引用、移动语义
对于c++11来说移动语义是一个重要的概念,一直以来我对这个概念都似懂非懂.最近翻翻资料感觉突然开窍,因此记下.其实搞懂之后就会发现这个概念很简单,并无什么高深的地方. 先说说右值引用.右值一般指的是 ...
- c++11之右值引用
本文大部分来自这里,并不是完全着行翻译,如有不明白的地方请参考原文. 在c++中,创建临时对象的开销对程序的影响一直很大,比如以下这个例子: String getName(){ return “Kia ...
- C++11 的右值引用
作者:Tinro链接:https://www.zhihu.com/question/22111546/answer/30801982来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...
- [转载]如何在C++03中模拟C++11的右值引用std::move特性
本文摘自: http://adamcavendish.is-programmer.com/posts/38190.htm 引言 众所周知,C++11 的新特性中有一个非常重要的特性,那就是 rvalu ...
- C++11之右值引用(一):从左值右值到右值引用
C++98中规定了左值和右值的概念,但是一般程序员不需要理解的过于深入,因为对于C++98,左值和右值的划分一般用处不大,但是到了C++11,它的重要性开始显现出来. C++98标准明确规定: 左值是 ...
- 【C/C++开发】C++11:右值引用和转发型引用
右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念.右值引用采用T&&这一语法形式,比传统的引用T&(如 ...
- C++11特性-右值引用
什么是左值,什么是右值 常见的误区有 = 左边的是左值,右边的是右值. 左值:具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables) ...
- C++11之右值引用(三):使用C++11编写string类以及“异常安全”的=运算符
前面两节,说明了右值引用和它的作用.下面通过一个string类的编写,来说明右值引用的使用. 相对于C++98,主要是多了移动构造函数和移动赋值运算符. 先给出一个简要的声明: class Strin ...
随机推荐
- k8spod资源的基础管理操作
pod是k8s api中的核心资源类型,它可以定义在json或yaml格式的资源清单中,由资源管理命令进行陈述式或声明式管理.创建时,用户可通过create或apply命令将请求提交到apiserve ...
- win10 去掉资源管理器左侧的Creative Cloud Files
open regedit 依次打开HKEY_CLASSES_ROOT\CLSID\{0E270DAA-1BE6-48F2-AC49-95A54E35F3C4} 双击{0E270DAA-1BE6-48F ...
- Python语法汇总
如果你之前学过任何一门编程语言,因为每种语言的基础语法要做的事情其实基本是相同的,只是表示方式或某些地方稍稍不同,因此在学Python的时候将它与其它你已经掌握的编程语言对比着学,这样学起来更快,效果 ...
- Angular7和leaflet一起使用时的作用域不一致
Angular7和leaflet一起使用时的作用域不一致问题,使用(e) =>可以完美解决. 使用原始的JavaScript: map.on("click", functio ...
- wx小程序知识点(二)
二.WXML和HTML的异同.WXSS和CSS的异同 (1)WXML和HTML 相同点:都是用来描述页面结构的,由标签.属性组成 不同点:标签名不一样,小程序标签名更少: 小程序多了 wx:if 这样 ...
- Linux 开启相关端口及查看已开启端口
防火墙层面: /sbin/iptables -I INPUT -p tcp --dport 8011 -j ACCEPT #开启8011端口 /etc/rc.d/init.d/iptables ...
- Unity3D_(插件)使用Camera渲染制作Minimap小地图
制作小地图:使用Camera渲染出来Render Texture 原理:使用摄像机从上到下获得场景游戏物体,摄像机Culling Mask渲染层级可设置是否需要在小地图上展示游戏物体,将摄像机获得的场 ...
- Linux 压缩方式测试
测试方法 使用 python 的 Faker 第三方包伪造数据,写入文件 test.txt 复制 test.txt 内容为 test2.txt ,将 test2.txt 的内容重定向到 test.tx ...
- 关于Jdk7与Jdk8对Collections进行分组的区别
先准备一点数据: public class User { private Integer id; private String type; private String name; ...
- IDEA 无法显示项目目录结构解决
不要去网上看什么乱七八糟的骚教程,一点用都没有.直接按下列步骤操作: 1. 关闭IDEA, 2. 然后删除项目文件夹下的.idea文件夹3. 重新用IDEA工具打开项目