1、左值与右值

  C++11中新增了一种类型,右值引用,标记为T &&。

  首先来介绍什么是左值和右值,左值是指表达式结束后依旧存在的持久对象,而右值是指表达式结束之后就不再存在的临时对象。一个区分左值与右值的简单方法就是:

  能不能对表达式取值,如果能,则是左值,否则为右值。所有具名变量或对象都是左值,右值不具名。

  其中,右值又有两个概念,将亡值和纯右值。非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式都是纯右值。将亡值在C++11中引进,将要移动的对象、T &&函数返回值、std::move返回值和转换为T &&的类型转换函数的返回值都是将亡值。

2、&&的特性

  右值引用是对一个右值进行引用,因为右值不具名,所以只能通过引用的方式找到。右值引用不拥有绑定对象的内存,所以必须立即初始化,右值引用其实就是延长右值临时变量的生命周期,只要该右值引用变量存在,该临时右值变量则会一直存在。

#include <iostream>

using namespace std;

int g_construct_count = ;
int g_copyconstruct_count = ;
int g_destruct_count = ; struct A
{
A()
{
cout << "construct:" << ++g_construct_count << endl;
} A(const A &a)
{
cout << "copyconstruct:" << ++g_copyconstruct_count << endl;
} ~A()
{
cout << "destruct:" << ++g_destruct_count << endl;
}
}; A GetA()
{
return A();
} int main()
{
A a = GetA(); return ;
}

  当GCC使用编译参数-fno-elide-constructors关闭返回值优化结果,会有以下输出结果:

construct:
copyconstruct:
destruct:
copyconstruct:
destruct:
destruct:

  如果开起返回值优化,那么结果如下:

construct:
destruct:

  这种优化是来自编译器优化,并不是C++标准,但是我们可以通过&&来做优化,也就是右值引用,当我们开起-fno-elide-constructors,再来看结果:

//调用代码
int main()
{
A &&a = GetA(); return ;
} //执行结果:
construct:
copyconstruct:
destruct:
destruct:

  通过右值引用,比之前要少一次拷贝构造和析构,也就是在A &&a = GetA();的时候发生的,在这个时候使临时变量的生命周期延长。
  其实不用C++11,使用C++98/03同样可以达到以上效果,将代码改成const A& a = GetA(),常量左值引用可以接收左值、右值、常量左值和常量右值,但是普通的左值引用不接受右值,如A& a = GetA()会编译不过,非常量左值只接受左值。
  T&&并不是表示右值,有可能表示左值,也有可能表示右值。但是T &&必须被初始化,被右值初始化就是右值,被左值初始化就是左值。

template<typename T>
void f(T && param); f(); //10是右值
int x = ;
f(x); //x是左值

  当有自动推导的时候(模板自动推导,auto),&&是一个未定义的引用类型。

template<typename T>
void f(T&& param);      //类型需要推导,&&是一个未定义引用类型 template<typename T>
class Test
{
Test(Test && ths); //右值,定义特定类型,没有类型推断
} void f(Test && param); //右值,定义确定类型,没有类型推断 template<typename T>
void f(std::vector<T> && param); //右值,vector<T>已经确定类型 template<typename T>
void f(const T && param); //右值,加上const修饰改变未定义引用

  当未定义引用类型仅在T&&的时候有效,任何附加条件都会使其失效变成一个普通的右值引用。这种类型变化在C++11中成为折叠引用,其规则如下:

  •   所有的右值引用叠加到右值引用还是右值引用;
  •   所有的其他引用类型之间的叠加都变成左值引用。
int &&var1 = x;         //var1->int &&,右值引用
auto &&var2 = var1;      //var2存在类型推导,未定义引用类型,var2->int &,左值引用 int i1,i2;
auto && v1 = i1;        //左值,被左值初始化
decltype(i1) && v2 = i2;    //error,右值引用不能被左值初始化

  对于左值初始化右值引用可以使用std::move;

decltype(i1) && v2 = std::move(i2);

  编译器会将已经命名的右值引用视为左值,未命名的右值视为右值。

#include <iostream>

using namespace std;

int g_lvalue = ;
int g_rvalue = ; void PrintValue(int &i)
{
cout << "Lvalue:" << i << endl;
} void PrintValue(int &&i)
{
cout << "Rvalue:" << i << endl;
} void Forward(int &&i)
{
PrintValue(i); //转发之后变成左值,右值变成一个命名对象,编译器当成左值
} int main()
{
int i = ;
PrintValue(i);
PrintValue();
Forward(); return ;
}

  执行结果:

Lvalue:
Rvalue:
Lvalue:

  总结:
  左值引用和右值引用独立于他们的类型,右值引用类型可能值左值也可能是右值;
  auto &&或函数参数类型自动推导的T &&是一个未定的引用类型,可能是左值引用也可能是右值引用,取决于初始化类型;
  所有的右值引用叠加到右值引用是一个右值引用,其它类型都为左值引用;
  编译器会将已命名的右值引用视为左值,未命名的右值引用视为右值。

2、右值引用,避免深拷贝

  当一个类含有堆内存,我们需要提供深拷贝的拷贝函数,如果使用默认构造函数会出现内存的重复删除。

#include <iostream>

class A
{
public:
A() :m_ptr(new int()){} ~A()
{
delete m_ptr;
}
private:
int *m_ptr;
}; int main()
{
A a;
A b; b = a; //运行出错
return ;
}

  上述例子中,a和b指向统一指针m_ptr,析构时重复删除该指针。正确的做法应该是提供深拷贝函数。

#include <iostream>

using namespace std;

class A
{
public:
A() :m_ptr(new int())
{
cout << "construct" << endl;
} ~A()
{
cout << "destruct" << endl;
delete m_ptr;
} A(const A & a):m_ptr(new int(*a.m_ptr))
{
cout << "copy construct" << endl;
} private:
int *m_ptr;
}; A GetA()
{
A a; return a;
} int main()
{
A a = GetA();
return ;
} //运行结果:
construct
copy construct
destruct
copy construct
destruct
destruct

  虽然可以解决问题,但是多次的拷贝确实不必要的,临时变量拷贝完就删除了,如果堆内存黑大,拷贝的代价就会很大。

#include <iostream>

using namespace std;

class A
{
public:
A() :m_ptr(new int())
{
cout << "construct" << endl;
} A(const A & a):m_ptr(new int(*a.m_ptr))
{
cout << "copy construct" << endl;
} A(A && a):m_ptr(a.m_ptr)
{
a.m_ptr = nullptr;
cout << "move construct" << endl;
} ~A()
{
cout << "destruct" << endl;
delete m_ptr;
} private:
int *m_ptr;
}; A GetA()
{
A a; return a;
} int main()
{
A a = GetA();
return ;
} //运行结果:
construct
move construct
destruct
move construct
destruct
destruct

  很明显,减少了一次拷贝构造,取而代之的是移动构造,在内存方面没有变化,避免了临时对象的深拷贝,提升了性能。这里A &&根据参数是左值还是右值来建立分支,如果是临时值,则会使用移动构造函数。
  移动语义可以将资源(堆、系统对象)通过浅拷贝方式从一个对象转移到另一个对象,减少不必要的临时对象的创建、拷贝和销毁,大幅提高性能。

C11性能之道:右值引用的更多相关文章

  1. C++11特性-右值引用

    什么是左值,什么是右值 常见的误区有 = 左边的是左值,右边的是右值. 左值:具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables) ...

  2. C++右值引用浅析

    一直想试着把自己理解和学习到的右值引用相关的技术细节整理并分享出来,希望能够对感兴趣的朋友提供帮助. 右值引用是C++11标准中新增的一个特性.右值引用允许程序员可以忽略逻辑上不需要的拷贝:而且还可以 ...

  3. C++ 11 中的右值引用

    C++ 11 中的右值引用 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream>    #include &l ...

  4. 【转】C++11 标准新特性: 右值引用与转移语义

    VS2013出来了,对于C++来说,最大的改变莫过于对于C++11新特性的支持,在网上搜了一下C++11的介绍,发现这篇文章非常不错,分享给大家同时自己作为存档. 原文地址:http://www.ib ...

  5. C++ 11 右值引用

    C++11中引入的一个非常重要的概念就是右值引用.理解右值引用是学习“移动语义”(move semantics)的基础.而要理解右值引用,就必须先区分左值与右值. 注意:左值右值翻译可能有些问题 *L ...

  6. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part4:强制Move语义

    本文为第四部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. 强制Move语义 众所周知,正如C++标准的第一修正案所陈述:“委 ...

  7. 最近新出的C++右值引用的意思

    看了一下这种方法的介绍,个人感觉,右值引用,更像人类的思想了,有些将编译前与编译后结合紧密的感觉. 左值引用是变量名的别名,右值引用是值的别名,也就是不将值赋给一个变量名所在的地址,直接将值所在的初始 ...

  8. C++ 11 右值引用以及std::move

    转载请注明出处:http://blog.csdn.net/luotuo44/article/details/46779063 新类型: int和int&是什么?都是类型.int是整数类型,in ...

  9. 【转】C++ 11 右值引用的理解

    右值引用的目的之一,是为了C++中一个比较影响性能的问题:拷贝临时对象,例如,在 int foo(){ ... } int x; x = foo(); 中,在第三句中,发生了以下的事情: 1.销毁 x ...

随机推荐

  1. 自动对象&局部静态对象

    一.关键点 对象的生命周期:程序执行过程中,该对象存在的那段时间 局部对象:形参.函数体内部定义的变量 二.自动对象 自动对象:只存在于块执行期间的对象 包括:局部变量.形参 三.局部静态对象 特点: ...

  2. window.open居中显示

    window.open中会发现怎么打开的浏览器窗口怎么不在正中间,而且还比较随性,那不是逼死强迫症患者了,所以查了一下资料始终打开的窗口能显示在正中间,下面是js中的代码分享 // url 转向网页的 ...

  3. shell练习题讲解

    写一个脚本,计算100以内所有的奇数的和以及所有偶数的和,分别显示出来#! /bin/bashsum1=0for i in `seq 1 2 100`do sum1=$[$sum1+$i]doneec ...

  4. Agile.Net 组件式开发平台 - 服务开发示例

    在上一篇文章中已经讲解了组件的开发,这篇文章讲解平台服务开发. Agile.Net开发管理平台项目,已经托管在开源中国码云平台(http://git.oschina.net) 登陆码云平台进入项目主页 ...

  5. FLT_MIN,FLT_MAX,FLT_EPSILON

    FLT_MIN,FLT_MAX,FLT_EPSILON  * min positive value */最小的正值#define FLT_MIN 1.175494351e-38F /* max val ...

  6. 【bzoj3427】Poi2013 Bytecomputer dp

    题目描述 A sequence of N  integers I1,I2…In from the set {-1,0,1} is given. The bytecomputer is a device ...

  7. 【周记:距gdoi43天】

    这个星期切了几道题吧,虽然说还是想让自己搏一搏,但是毕竟自己弱嘛,而且很多东西都还没熟透&不像rausen大神都屠进前100了. 加油吧.

  8. Spark2.1.0之初识Spark

    随着近十年互联网的迅猛发展,越来越多的人融入了互联网——利用搜索引擎查询词条或问题:社交圈子从现实搬到了Facebook.Twitter.微信等社交平台上:女孩子们现在少了逛街,多了在各大电商平台上的 ...

  9. 关于PDO取得结果集的数据类型为string的问题

    很久没写些什么了, 正好今天工作中遇到了以前在意过的问题. 之前曾注意到过,php从数据库中取得的结果集后,其中的字段全都会变成string类型.今儿通过ajax调取数据, 有一个type字段是int ...

  10. UVA.297 Quadtrees (四分树 DFS)

    UVA.297 Quadtrees (四分树 DFS) 题意分析 将一个正方形像素分成4个小的正方形,接着根据字符序列来判断是否继续分成小的正方形表示像素块.字符表示规则是: p表示这个像素块继续分解 ...