C++11中引入的一个非常重要的概念就是右值引用。理解右值引用是学习“移动语义”(move semantics)的基础。而要理解右值引用,就必须先区分左值与右值。

注意:左值右值翻译可能有些问题

    *L-value中的L指的是location,表示可寻址。

    *R-value中的R指的是read,表示可读。

  对左值和右值的一个最常见的误解是:赋值运算符左边的就是左值,赋值运算符右边的就是右值。左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象。一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。下面给出一些例子来进行说明:

 int a = ;
int b = ;
int *ptr = &a;
vector<int> vec;
vec.push_back();
string str1 = "Hello ";
string str2 = "world";
const int& m = ;

  请问,a、b、a+b、a++、++a、ptr、*ptr、vec[0]、100、string("Hello")、str1、str1+str2、m分别是左值还是右值?

  a和b都是持久对象(可以对其取地址),是左值;

  a+b是临时对象(不可以对其取地址),是右值;

  a++是先取出持久对象a的一份拷贝,再使持久对象a的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值;

  ++a则是使持久对象a的值加1,并返回那个持久对象a本身(可以对其取地址),故其是左值;

  ptr和*ptr都是持久对象(可以对其取地址),是左值;

  vec[0]调用重载的[]操作符,而[]操作符返回的是一个int&,为持久对象(可以对其取地址),是左值;

  100和string("Hello")是临时对象(不可以对其取地址),是右值;

  str1是持久对象(可以对其取地址),是左值;

  str1 + str2 是调用了+操作符,而+操作符返回的是一个string(不可以对其取地址),故其为右值;

  m是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。

  区分清楚了左值与右值,再来看看左值引用。左值引用根据其修饰符的不同,可以分为非常量左值引用和常量左值引用。

  非常量左值引用只能绑定非常量左值,不能绑定常量左值、非常量右值和常量右值。如果允许绑定到常量左值和常量右值,则非常量左值引用可以用于修改常量左值和常量右值,这明显违反了常量的含义。如果允许绑定到非常量右值,则会导致非常危险的情况出现,因为非常量右值是一个临时对象,非常量左值引用可能会使用一个已经被销毁了的临时对象。

  常量左值引用可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。

  可以看出,使用左值引用时,无法区分出绑定的是否是非常量右值的情况。那么,为什么要对非常量右值进行区分呢,区分出来又有什么好处呢?这就牵涉到C++中一个著名的性能问题——拷贝临时对象。考虑下面的代码:

 vector<int>  GetAllScores()
{
vector<int> vctTemp;
vctTemp.push_back();
vctTemp.push_back();
return vctTemp;
}

  当使用vector<int> vctScore = GetAllScores()进行初始化时,实际上调用了三次构造函数。尽管有些编译器可以采用RVO(Return Value Optimization)来进行初始化,但优化工作只在某些特定条件下才能进行。可以看到,上面很普通的一个函数调用,由于存在临时对象的拷贝,导致了额外的两次拷贝构造函数和析构函数的开销。当然,我们也可以修改函数的形式为void GetAllScores(vector<int> &vctScore),但这并不一定就是我们需要的形式。另外,考虑下面的字符串的链接操作:
  

 string s1("hello");
string s = s1 + "a" + "b" + "c" + "d" + "e";

  在对s进行初始化时,会产生大量的临时变量,并涉及到大量字符串的拷贝操作,这显然会影响程序的效率和性能。怎么解决这个为题呢?如果能确定某个值是一个非常量右值(或者是一个以后不会再使用的左值),则在进行临时对象的拷贝时,可以不用拷贝实际的数据,而只是“窃取”指向实际数据的指针(类似于STL中的auto_ptr,会转移所有权)。C++11中引入的右值引用正好可用于标识一个非常量右值。C++11中用&表示左值引用,用&&表示右值引用,如:

int &&a = ;

  右值引用根据其修饰符的不同,也可以分为非常量右值引用和常量右值引用。

  非常量右值引用只能绑定非常量右值,不能绑定到非常量左值、常量左值和常量右值。如果允许绑定到非常量左值,则可能会错误地窃取一个持久对象的数据,而这是非常危险的;如果允许绑定到常量左值和常量右值,则非常量右值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义。

  常量右值引用可以绑定到非常量右值和常量右值,不能绑定到非常量左值和常量左值(理由同上)。

  有了右值引用的概念,我们就可以用它来实现下面的CMyString类。

 class CMyString
{
public:
CMyString(const char* str = nullptr)
//构造函数
{
cout << "CMyString(const char *str = nullptr)" << endl;
if (str == nullptr)
{
m_pData = new char[];
*m_pData = '\0';
}
else
{
m_pData = new char[strlen(str) + ];
strcpy(m_pData, str);
}
} CMyString(const CMyString& s)
//拷贝构造函数
{
cout << "CMyString(const CMyString &s)" << endl;
m_pData = new char[strlen(s.m_pData) + ];
strcpy(m_pData, s.m_pData);
} CMyString(CMyString &&s)
//move构造函数
{
cout << "CMyString(CMyString &&s)" << endl;
m_pData = s.m_pData;
s.m_pData = nullptr;
} ~CMyString()
//析构函数
{
cout << "~CMyString()" << endl;
delete [] m_pData;
m_pData = nullptr;
} CMyString& operator=(const CMyString& s)
//拷贝赋值函数
{
cout << "CMyString& operator=(const CMyString& s)" << endl;
if (this != & s) {
delete [] m_pData;
m_pData = new char[strlen(s.m_pData) + ];
strcpy(m_pData, s.m_pData);
}
return *this;
} CMyString& operator=(CMyString&& s)
//move赋值函数
{
cout << "CMyString& operator=(CMyString&& s)" << endl;
if (this != &s) {
delete [] m_pData;
m_pData = s.m_pData;
s.m_pData = nullptr;
}
return *this;
}
private:
char *m_pData;
};

  可以看到,上面添加了move版本的构造函数和赋值函数。那么,添加move版本后,对类的自动生成规则有什么影响呢?唯一的影响就是,如果提供了move版本的构造函数,则不会生成默认的构造函数。另外,编译器永远不会自动生成move版本的构造函数和赋值函数,它们需要显式地添加。

  当添加了move版本的构造函数和赋值函数的重载形式后,某一个函数调用应当使用哪一个重载版本呢?下面是按照判决的优先级列出的3条规则:

    1、常量值只能绑定到常量引用上,不能绑定到非常量引用上;

    2、左值优先绑定到左值引用上,右值优先绑定到右值引用上;

    3、非常量值优先绑定到非常量引用上。

  当给构造函数或赋值函数传入一个非常量右值时,依据上面给出的判决规则,可以得出会调用move版本的构造函数或赋值函数。而在move版本的构造函数或赋值函数内部,都是直接“移动了”其内部数据的指针(因为它是非常量右值,是一个临时对象,移动了其内部数据的指针不会导致任何问题,它马上就要被销毁了,只是重复利用了其内存),这样就省去了拷贝数据的大量开销。

  一个需要注意的地方是,拷贝构造函数可以通过直接调用*this = s来实现,但move构造函数却不能。这是因为在move构造函数中,s虽然是一个非常量右值引用,但其本质却是一个左值(是持久对象,可以对其取地址),因此调用*this = s时,会使用拷贝赋值函数而不是move赋值函数,而这已与move构造函数的语义不符。要使语义正确,需要将左值绑定到非常量右值引用上,C++11提供了move()函数来实现这种转换,因此可以修改为*this = move(s),这样move构造函数就会调用move赋值函数。

C++ 11 右值引用的更多相关文章

  1. C++11右值引用

    [C++11右值引用] 1.什么是左值?什么是右值? 左值是表达式结束后依然存在的对象:右值是表达式结束时就不再存在的对象. 2.std::move的作用是什么? std::move用于把任意类型转化 ...

  2. 关于C++11右值引用和移动语义的探究

    关于C++11右值引用和移动语义的探究

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

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

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

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

  5. C++11右值引用和std::move语句实例解析

    关键字:C++11,右值引用,rvalue,std::move,VS 2015 OS:Windows 10 右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一.从实践 ...

  6. C++11 右值引用和转移语义

    新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和 ...

  7. c++11——右值引用

    1. 左值和右值 左值是表达式结束之后仍然存在的持久化对象,而右值是指表达式结束时就不再存在的临时对象.     c++11中,右值分为两种类型:将亡值(xvalue, expiring value) ...

  8. C++11 右值引用 与 转移语义

    新特性的目的 右值引用(R-value Reference)是C++新标准(C++11, 11代表2011年)中引入的新特性,它实现了转移语义(Move Semantics)和精确传递(Perfect ...

  9. c++11 右值引用和移动语义

    什么是左值.右值 最常见的误解: 等号左边的就是左值,等号右边的就是右值 左值和右值都是针对表达式而言的, 左值是指表达式结束后依然存在的持久对象 右值是指表达式结束时就不再存在的临时对象区分: 能对 ...

随机推荐

  1. 最流行的编程语言JavaScript能做什么?

    本文转自互联网! 首先很遗憾的一点是,"PHP虽然是最好的语言",但是它不是最流行的语言. 对不起的还有刚刚在4月TIOBE编程语言排行榜上榜的各个语言: 你们都很棒,但是你们都担 ...

  2. #在lua中的运用

    在lua中"#"表示返回表长度或字符串长度 例子一: a = "Hello " b = "World" print("Concat ...

  3. 2016年12月8日 星期四 --出埃及记 Exodus 21:3

    2016年12月8日 星期四 --出埃及记 Exodus 21:3 If he comes alone, he is to go free alone; but if he has a wife wh ...

  4. iOS之地理位置及定位系统 -- 入门笔记

    这是因为xcode升级造成的定位权限设置问题.升级xcode6.xcode7以后打开以前xcode5工程,程序不能定位.工程升级到xcode6或xcode7编译时需要iOS8 要自己写授权,不然没权限 ...

  5. SpringMVC拦截器2(资源和权限管理)(作为补充说明)

    SpringMVC拦截器(资源和权限管理) 1.DispatcherServlet SpringMVC具有统一的入口DispatcherServlet,所有的请求都通过DispatcherServle ...

  6. VS为VC++添加UAC控制(VC程序默认管理员运行)

    1.VS编译链接VC++工程生成文件默认是没有管理员权限的 生成的程序图标是没有盾牌的如图: 如果程序需要在C盘做些写入操作 就必须具有管理员权限 所以需要在工程中进行设置. 注:自己项目的安装程序安 ...

  7. CodeForces 34B Sale

    Sale Time Limit:2000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u Submit Status ...

  8. Create XO Checker Game With Oracle Forms

    Created XO Checker game in Oracle Forms and sharing its FMB (source code) for reference so that you ...

  9. Burpsuite之Http Basic认证爆破

    有的时候经常遇到401.今天正好朋友问怎么爆破,也顺便记录一下 怕忘记了 referer:http://www.2cto.com/Article/201303/194449.html 看到Burpsu ...

  10. [SAP ABAP开发技术总结]ABAP程序之间数据共享与传递

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...