C++11的右值引用的具体使用

C++11 引入了 std::move 语义、右值引用、移动构造和完美转发这些特性。由于这部分篇幅比较长,分为3篇来进行阐述。

在了解这些特性之前,我们先来引入一些问题。

一、问题导入

  1. 函数返回值是传值的时候发生几次对象构造、几次拷贝?
  2. 函数的形参是值传递的时候发生几次对象构造?

让我们先来看一段代码,

// 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 省略优化,则还要考虑临时对象的拷贝。

事实上,在未经优化的情况下,以下时候拷贝构造函数会被调用:

  1. 函数内的局部对象做为返回值返回(不是引用)的时候会发生拷贝(拷贝为临时对象返回)
  2. 函数形参为传值的时候,会发生拷贝构造
  3. 一个对象以另外一个对象进行初始化的时候

对象的频繁构造是程序的开销,特别是当对象内部有堆上内存(比如有 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》, 机械工业出版社

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 浅析C++11中的右值引用、转移语义和完美转发

    1. 左值与右值: C++对于左值和右值没有标准定义,但是有一个被广泛认同的说法:可以取地址的,有名字的,非临时的就是左值;不能取地址的,没有名字的,临时的就是右值. 可见立即数,函数返回的值等都是右值;而非匿名对象(包括变量),函数返回的引用,const对象等都是左值. 从本质上理解,创建和销毁由编译器幕后控制的,程序员只能确保在本行代码有效的,就是右值(包括立即数);而用户创建的,通过作用域规则可知其生存期的,就是左值(包括函数返回的局部变量的引用以及const对象),例如: int& fo

  • C++11右值引用和转发型引用教程详解

    右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念.右值引用采用T&&这一语法形式,比传统的引用T&(如今被称作左值引用 lvalue reference)多一个&. 如果把经由T&&这一语法形式所产生的引用类型都叫做右值引用,那么这种广义的右值引用又可分为以下三种类型: 无名右值引用 具名右值引用 转发型引用 无名右值引用和具名右值引用的引入主要是为了解决移动语义问题. 转发型引用的引

  • 详解C++11中的右值引用与移动语义

    C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

  • C++11右值引用和std::move语句实例解析(推荐)

    右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一.从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题.从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷.从库设计者的角度讲,它给库设计者又带来了一把利器.从库使用者的角度讲,不动一兵一卒便可以获得"免费的"效率提升- 下面用实例来深入探讨右值引用. 1.什么是左值,什么是右值,简单说左值可以赋值,右值不可以赋值.以下面代码为例,"A a = getA();&q

  • C++11的右值引用的具体使用

    C++11 引入了 std::move 语义.右值引用.移动构造和完美转发这些特性.由于这部分篇幅比较长,分为3篇来进行阐述. 在了解这些特性之前,我们先来引入一些问题. 一.问题导入 函数返回值是传值的时候发生几次对象构造.几次拷贝? 函数的形参是值传递的时候发生几次对象构造? 让我们先来看一段代码, // main.cpp #include <iostream> using namespace std; class A{ public: A(){ cout<<"cla

  • C++11右值引用和移动语义的实例解析

    目录 基本概念 左值 vs 右值 左值引用 vs 右值引用 右值引用使用场景和意义 左值引用的使用场景 左值引用的短板 右值引用和移动语义 右值引用引用左值 右值引用的其他使用场景 完美转发 万能引用 完美转发保持值的属性 完美转发的使用场景 总结 基本概念 左值 vs 右值 什么是左值? 左值是一个表示数据的表达式,如变量名或解引用的指针. 左值可以被取地址,也可以被修改(const修饰的左值除外). 左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边. int main() { //以

  • 深入解读C++中的右值引用

    右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一,这点从该特性的提案在C++ - State of the Evolution列表上高居榜首也可以看得出来. 从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题.从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷.从库设计者的角度讲,它给库设计者又带来了一把利器.从库使用者的角度讲,不动一兵一卒便可以获得"免费的"效率提升- 在标准C++语言中,临时量(术语为右值,因其出

  • C++11 模板参数的“右值引用”是转发引用吗

    在C++11中,&&不再只有逻辑与的含义,还可能是右值引用: void f(int&& i); 但也不尽然,&&还可能是转发引用: template<typename T> void g(T&& obj); "转发引用"(forwarding reference)旧称"通用引用"(universal reference),它的"通用"之处在于你可以拿一个左值绑定给转发引用

  • 深入了解c++11 移动语义与右值引用

    1.移动语义 C++11新标准中一个最主要的特性就是提供了移动而非拷贝对象的能力.如此做的好处就是,在某些情况下,对象拷贝后就立即被销毁了,此时如果移动而非拷贝对象会大幅提升性能.参考如下程序: //moveobj.cpp #include <iostream> #include <vector> using namespace std; class Obj { public: Obj(){cout <<"create obj" << e

  • 一文搞懂C++11万能引用和右值引用

    目录 前言 正文 万能引用 结语 参考: 前言 我们通过一个问题来进入今天的话题:1.形如 “type&&” 的结构,就是右值引用吗?2.以下哪些属于右值引用? ① void fun(Widget && param);② Widget && var1= Widget();③ auto && var2 = var1;④ template<typename T> void f(std::vector<T>&&

随机推荐