C++引用现在分为左值引用(能取得其地址)和 右值引用(不能取得其地址)。其实很好理解,左值引用中的左值一般指的是出现在等号左边的值(带名称的变量,带*号的指针等一类的数据),程序能对这样的左值进行引用获得其地址;右值引用中的右值一般指的就是出现在等号右边的值(右值引用:常量、表达式、函数非左值引用的返回值),程序不能对这样的右值进行引用获得其地址。

引入右值引用的目的之一是实现移动语义。

(1)移动语义的引入是为了解决在进行大数据复制的时候,将动态申请的内存空间的所有权直接转让出去,不用进行大量的数据移动,既节省空间又提高效率

要实现移动语义,就必须让编译器知道什么时候复制,什么时候移动语义,而这就是右值引用发挥作用的地方。移动语义可能修改右值的值,所以,右值引用参数不能是const

(2)通过构造复制构造函数来实现复制语义,通过移动构造函数来实现移动语义。复制构造使用const &引用,而移动构造函数使用非const && 引用

(3)被移动语义的数据交出了所有权,为了不出现析构两次同一数据区,要将交出所有权的数据的指向动态申请内存区的指针赋值位nullptr,即空指针,对空指针执行delete[]是合法的。

A(A && h) : a(h.a)
{
h.a = nullptr; //C++11 新的空指针表示nullptr
}

编译器判断构造函数中是左值还是右值,然后调用相应的复制构造函数或者移动构造函数来构造数据。

(4)移动赋值操作符:他的原理跟移动构造函数相同,如下。

A & operator = (A&& h)
{
assert(this != &h); a = nullptr;
a = move(h.a);
h.a = nullptr;
return *this;
}

(5)强制移动,就是让左值使用移动构造函数,强制让其交出所有权。Utility文件中声明,std::move()函数(将左值强制转换成右值引用)。

(6)这里要注意的是异常发生的情况,要尽量保证移动构造函数不发生异常,可以通过noexcept关键字,这里可以保证移动构造函数中抛出来的异常会直接调用terminate终止程序。详见《C++ Primer Plus》

总结:利用匿名的变量,让其交出所有权,避免复制数据,可以提高程序的效率,因此,如果一个临时变量再也用不着了,可以让其强制移动语义,这样,程序不用再进行大量的数据复制了,尤其是在vector作为返回值的时候。

《C++ Primer Plus》涉及右值引用:P626.const & 和临时变量;p695.decltype(C++11 新. 类型推断)

【下文转自】http://blog.csdn.net/yusiguyuan/article/details/38616821

1、右值引用引入的背景

临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题。但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了Copy Elision、RVO(包括NRVO)等编译器优化技术,它们可以防止某些情况下临时对象产生和拷贝。下面简单地介绍一下Copy Elision、RVO,对此不感兴趣的可以直接跳过:

(1) Copy Elision

Copy Elision技术是为了防止某些不必要的临时对象产生和拷贝,例如:

struct A {
A(int) {}
A(const A &) {}
};
A a = ;

理论上讲,上述A a = 42;语句将分三步操作:第一步由42构造一个A类型的临时对象,第二步以临时对象为参数拷贝构造a,第三步析构临时对象。如果A是一个很大的类,那么它的临时对象的构造和析构将造成很大的内存开销。我们只需要一个对象a,为什么不直接以42为参数直接构造a呢?Copy Elision技术正是做了这一优化。

【说明】:你可以在A的拷贝构造函数中加一打印语句,看有没有调用,如果没有被调用,那么恭喜你,你的编译器支持Copy Elision。但是需要说明的是:A的拷贝构造函数虽然没有被调用,但是它的实现不能没有访问权限,不信你将它放在private权限里试试,编译器肯定会报错。

(2) 返回值优化(RVO,Return Value Optimization)

返回值优化技术也是为了防止某些不必要的临时对象产生和拷贝,例如:

struct A {
A(int) {}
A(const A &) {}
};
A get() {return A();}
A a = get();

理论上讲,上述A a = get();语句将分别执行:首先get()函数中创建临时对象(假设为tmp1),然后以tmp1为参数拷贝构造返回值(假设为tmp2),最后再以tmp2为参数拷贝构造a,其中还伴随着tmp1和tmp2的析构。如果A是一个很大的类,那么它的临时对象的构造和析构将造成很大的内存开销。返回值优化技术正是用来解决此问题的,它可以避免tmp1和tmp2两个临时对象的产生和拷贝。

【说明】: a)你可以在A的拷贝构造函数中加一打印语句,看有没有调用,如果没有被调用,那么恭喜你,你的编译器支持返回值优化。但是需要说明的是:A的拷贝构造函数虽然没有被调用,但是它的实现不能没有访问权限,不信你将它放在private权限里试试,编译器肯定会报错。

b)除了返回值优化,你可能还听说过一个叫具名返回值优化(Named Return Value Optimization,NRVO)的优化技术,从程序员的角度而言,它其实跟RVO同样的逻辑。只是它的临时对象具有变量名标识,例如修改上述get()函数为:

A get() {
A tmp(); // #1
// do something
return tmp;
}
A a = get(); // #2

想想上述修改后A类型共有几次对象构造?虽然#1处看起来有一次显示地构造,#2处看起来也有一次显示地构造,但如果你的编译器支持NRVO和Copy Elision,你会发现整个A a = get();语句的执行过程,只有一次A对象的构造。如果你在get()函数return语句前打印tmp变量的地址,在A a = get();语句后打印a的地址,你会发现两者地址相同,这就是应用了NRVO技术的结果。

(3) Copy Elision、RVO无法避免的临时对象的产生和拷贝

虽然Copy Elision和NVO(包括NRVO)等技术能避免一些临时对象的产生和拷贝,但某些情况下它们却发挥不了作用,例如:

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

我们只是想交换a和b两个对象所拥有的数据,但却不得不使用一个临时对象tmp备份其中一个对象,如果T类型对象拥有指向(或引用)从堆内存分配的数据,那么深拷贝所带来的内存开销是可以想象的。为此,C++11标准引入了右值引用,使用它可以使临时对象的拷贝具有move语意,从而可以使临时对象的拷贝具有浅拷贝般的效率,这样便可以从一定程度上解决临时对象的深度拷贝所带来的效率折损。

2、C++03标准中的左值与右值

要理解右值引用,首先得区分左值(lvalue)和右值(rvalue)。

C++03标准中将表达式分为左值和右值,并且“非左即右”:

Every expression is either an lvalue or an rvalue.

区分一个表达式是左值还是右值,最简便的方法就是看能不能够对它取地址:如果能,就是左值;否则,就是右值。

【说明】:由于右值引用的引入,C++11标准中对表达式的分类不再是“非左即右”那么简单,不过为了简单地理解,我们暂时只需区分左值右值即可,C++11标准中的分类后面会有描述。

3、右值引用的绑定规则

右值引用(rvalue reference,&&)跟传统意义上的引用(reference,&)很相似,为了更好地区分它们俩,传统意义上的引用又被称为左值引用(lvalue reference)。下面简单地总结了左值引用和右值引用的绑定规则(函数类型对象会有所例外):

(1)非const左值引用只能绑定到非const左值;
(2)const左值引用可绑定到const左值、非const左值、const右值、非const右值;
(3)非const右值引用只能绑定到非const右值;
(4)const右值引用可绑定到const右值和非const右值。

测试例子如下:

struct A { A(){} };
A lvalue; // 非const左值对象
const A const_lvalue; // const左值对象
A rvalue() {return A();} // 返回一个非const右值对象
const A const_rvalue() {return A();} // 返回一个const右值对象 // 规则一:非const左值引用只能绑定到非const左值
A &lvalue_reference1 = lvalue; // ok
A &lvalue_reference2 = const_lvalue; // error
A &lvalue_reference3 = rvalue(); // error
A &lvalue_reference4 = const_rvalue(); // error // 规则二:const左值引用可绑定到const左值、非const左值、const右值、非const右值
const A &const_lvalue_reference1 = lvalue; // ok
const A &const_lvalue_reference2 = const_lvalue; // ok
const A &const_lvalue_reference3 = rvalue(); // ok
const A &const_lvalue_reference4 = const_rvalue(); // ok // 规则三:非const右值引用只能绑定到非const右值
A &&rvalue_reference1 = lvalue; // error
A &&rvalue_reference2 = const_lvalue; // error
A &&rvalue_reference3 = rvalue(); // ok
A &&rvalue_reference4 = const_rvalue(); // error // 规则四:const右值引用可绑定到const右值和非const右值,不能绑定到左值
const A &&const_rvalue_reference1 = lvalue; // error
const A &&const_rvalue_reference2 = const_lvalue; // error
const A &&const_rvalue_reference3 = rvalue(); // ok
const A &&const_rvalue_reference4 = const_rvalue(); // ok // 规则五:函数类型例外
void fun() {}
typedef decltype(fun) FUN; // typedef void FUN();
FUN & lvalue_reference_to_fun = fun; // ok
const FUN & const_lvalue_reference_to_fun = fun; // ok
FUN && rvalue_reference_to_fun = fun; // ok
const FUN && const_rvalue_reference_to_fun = fun; // ok

【说明】:(1) 一些支持右值引用但版本较低的编译器可能会允许右值引用绑定到左值,例如g++4.4.4就允许,但g++4.6.3就不允许了,clang++3.2也不允许,据说VS2010 beta版允许,正式版就不允许了,本人无VS2010环境,没测试过。

(2)右值引用绑定到字面值常量同样符合上述规则,例如:int &&rr = 123;,这里的字面值123虽然被称为常量,可它的类型为int,而不是const int。对此C++03标准文档4.4.1节及其脚注中有如下说明:

    If T is a non-class type, the type of the rvalue is the cv-unqualified version of T.
    In C++ class rvalues can have cv-qualified types (because they are objects). This differs from ISO C, in which non-lvalues never have cv-qualified types.

因此123是非const右值,int &&rr = 123;语句符合上述规则三。

此,我们已经了解了不少右值引用的知识点了,下面给出了一个完整地利用右值引用实现move语意的例子:

#include <iostream>
#include <cstring> #define PRINT(msg) do { std::cout << msg << std::endl; } while(0) template <class _Tp> struct remove_reference {typedef _Tp type;};
template <class _Tp> struct remove_reference<_Tp&> {typedef _Tp type;};
template <class _Tp> struct remove_reference<_Tp&&> {typedef _Tp type;}; template <class _Tp>
inline typename remove_reference<_Tp>::type&& move(_Tp&& __t) {
typedef typename remove_reference<_Tp>::type _Up;
return static_cast<_Up&&>(__t);
} class A {
public:
A(const char *pstr) {
PRINT("constructor");
m_data = (pstr != ? strcpy(new char[strlen(pstr) + ], pstr) : );
}
A(const A &a) {
PRINT("copy constructor");
m_data = (a.m_data != ? strcpy(new char[strlen(a.m_data) + ], a.m_data) : );
}
A &operator =(const A &a) {
PRINT("copy assigment");
if (this != &a) {
delete [] m_data;
m_data = (a.m_data != ? strcpy(new char[strlen(a.m_data) + ], a.m_data) : );
}
return *this;
}
A(A &&a) : m_data(a.m_data) {
PRINT("move constructor");
a.m_data = ;
}
A & operator = (A &&a) {
PRINT("move assigment");
if (this != &a) {
m_data = a.m_data;
a.m_data = ;
}
return *this;
}
~A() { PRINT("destructor"); delete [] m_data; }
private:
char * m_data;
}; void swap(A &a, A &b) {
A tmp(move(a));
a = move(b);
b = move(tmp);
} int main(int argc, char **argv, char **env) {
A a(""), b("");
swap(a, b);
return ;
}

输出结果为:

constructor
constructor
move constructor
move assigment
move assigment
destructor
destructor
destructor

C++11新特性之0——移动语义、移动构造函数和右值引用的更多相关文章

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

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

  2. 对C++11中的`移动语义`与`右值引用`的介绍与讨论

    本文主要介绍了C++11中的移动语义与右值引用, 并且对其中的一些坑做了深入的讨论. 在正式介绍这部分内容之前, 我们先介绍一下rule of three/five原则, 与copy-and-swap ...

  3. [c++11]右值引用、移动语义和完美转发

    c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...

  4. [转][c++11]我理解的右值引用、移动语义和完美转发

    c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...

  5. [转载] C++11中的右值引用

    C++11中的右值引用 May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导 ...

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

    原文出处:http://kuring.me/post/cpp11_right_reference May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移 ...

  7. C++11的左值引用与右值引用总结

    概念 在C++11中,区别表达式是左值或右值可以做这样的总结:当一个对象被用作右值的时候,用的是对象的值(内容):当对象被用作左值的时候,用的是对象的身份(在内存中的位置).左值有持久的状态,而右值要 ...

  8. C++11左值引用和右值引用

    转载:https://www.cnblogs.com/golaxy/p/9212897.html C++11的左值引用与右值引用总结 概念 1.&与&&  对于在C++中,大家 ...

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

    文章出处:https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/ 新特性的目的 右值引用 (Rvalue Referene) ...

随机推荐

  1. ThinkPHP框架快捷键使用说明

    ThinkPHP框架快捷键使用说明 php mvc框架ThinkPHP中有很多快捷键,但是很多时候我们不太明白它的意思,下面我简单的列了下他们的含义: A快速实例化Action类库 B执行行为类 C配 ...

  2. 【火狐FireFox】同步失败后,书签被覆盖,如何恢复书签

    问题场景: 使用公司的电脑,下载安装火狐,登录个人帐号后,火狐会自动开始同步书签.但有时候会同步失败,比如登录之前选的是[本地服务],而最新的书签都是在[全球服务]理,那么很有可能同步到的是N久之前的 ...

  3. 【WPF】设置ListBox容器Item的流式布局

    需求:像下图那样显示把一组内容装入ListBox中显示.要求用WrapPanel横向布局,顺序如图中的数字. 问题:ListBox默认的布局是从上往下单列的,所以需要设置布局. <ListBox ...

  4. 【WPF】样式与模板:鼠标移入/悬浮时按钮的背景色不改变

    情况:鼠标移到按钮上,默认情况是按钮背景色会改变的,网上也能搜到很多如何自定义改变的背景色. 需求:现在需求反过来,想要鼠标移到按钮上,保持按钮的背景色不改变. 一种思路:在样式文件中,使用Multi ...

  5. Android——用对话框做登陆界面(自定义对话框AlertDialog,多线程,进度条ProgressDialog,ListView,GridView,SharedPreferences存,读数据,存取文本,assets文件)

    效果: 1.点击图标进入页面二 2.页面2图片暂停显示5秒进入页面三 3.点击页面三登陆按钮,打开登陆对话框,输入密码进入页面四 点击下载按钮,显示水平进度条 点击保存和获取用户名和密码 进入页面六  ...

  6. Android各版本重要变动记录

    本文来自同步博客. Android M:运行时权限 运行时权限属于比较熟悉的话题不深入展开.除了support包可以让应用完成运行时权限,github上也有好多扩展.用得比较多的是Google官方的E ...

  7. UVALive-4670 AC自动机入门题 求出现次数最多的子串

    /** 链接:http://vjudge.net/problem/UVALive-4670 详见lrj训练指南P216 */ #include<bits/stdc++.h> using n ...

  8. Hibernate- 连接查询

    01.搭建开发环境 02.连接查询 package com.gordon.test; import java.util.Arrays; import java.util.List; import or ...

  9. libmysqlclient16 libmysqlclient-dev

    如果提示安装 libmysqlclient16 则用 libmysqlclient-dev 代替之

  10. ubuntu下配置java环境

    配置JDK环境 下载 登录oracle的网站去下载JDK7 http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads ...