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. exit和wait一起可以彻底清除子进程的资源

    #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<stdlib.h> ...

  2. linux定时任务crontab设置

    crontab是linux下的定时任务,类似于window下的计划任务: crontab -l ##查询任务列表 crontab -e ##编辑定时任务 首先准备好要执行的脚本monitor_fs.s ...

  3. /etc/fstab下的挂载类型defaults默认参数

    [root@ob2 data]# cat /etc/fstab -98c0-59dbbcf8b040 / ext4 defaults UUID=e4ab7a0c-500f--bcd2-a77be0ce ...

  4. Restful --- 让JSON回归单纯

    设计模式才是软件哲学的根本.. 一种软件架构风格.设计风格,而不是标准,只是提供了一组设计原则和约束条件.它主要用于客户端和服务器交互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓 ...

  5. 轻量级ORM框架Dapper应用三:使用Dapper实现In操作

    IN 操作符允许我们在 WHERE 子句中规定多个值. 本篇文章中,还是使用和上篇文章中同样的实体类和数据库,Dapper使用in操作符的代码如下: using System; using Syste ...

  6. mac配置完ssh依然提示"Enter passphrase for key"解决方法

    使用Git,每次都提示输入命令. 这个问题折磨很久,明明已经配置过ssh了,可是每次还要提示输入密码,从网上查查,最后一条命令解决问题: 问题提示: Enter passphrase for key ...

  7. 12款优秀 jQuery Ajax 分页插件和教程

    12款优秀 jQuery Ajax 分页插件和教程 在这篇文章中,我为大家收集了12个基于 jQuery 框架的 Ajax 分页插件,这些插件都提供了详细的使用教程和演示.Ajax 技术的出现使得 W ...

  8. 试读《基于MVC的JavaScript Web富应用开发》— 不一样的JavaScript

    前言 <基于MVC的JavaScript Web富应用开发>是ItEye在7月份发起试读的书.下载了试读的章节,发现只有全本的开始到第二章,第一章很简洁明了地讲述了JavaScript的历 ...

  9. Mastering the game of Go with deep neural networks and tree search浅析

    Silver, David, et al. "Mastering the game of Go with deep neural networks and tree search." ...

  10. php跨form提交方法

    1.php curl function curlPost($url,$params) { $postData = ''; foreach($params as $k => $v) { $post ...