PS: 通过引入接收右值的函数形参,可以通过接收右值来实现高效

PS在C++11中,标准库在<utility>中提供了一个有用的函数std::move,这个函数的名字具有迷惑性,因为实际上 std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而我们可以通过右值引用使用该值,以用于移动语义。从实现上 讲,std::move基本等同于一个类型转换:static_cast<T&&>(lvalue);

C++ 11带来了move语义,可以有效的提高STL的效率,这篇文章写的非常好,可以参考,这里对原文进行翻译,加入我自己的理解

原文:http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html

先看看这个例子:

  1. #include <iostream>
  2. using namespace std;
  3. vector<int> doubleValues (const vector<int>& v)
  4. {
  5. vector<int> new_values( v.size() );
  6. for (auto itr = new_values.begin(), end_itr = new_values.end(); itr != end_itr; ++itr )
  7. {
  8. new_values.push_back( 2 * *itr );
  9. }
  10. return new_values;
  11. }
  12. int main()
  13. {
  14. vector<int> v;
  15. for ( int i = 0; i < 100; i++ )
  16. {
  17. v.push_back( i );
  18. }
  19. v = doubleValues( v );
  20. }

调用函数doubleValues时是有两次复制的,一次是在函数返回时,因为local变量要被回收,所以需要copy构造一个临时对象来返回,当返回这个临时对象后,又需要一次copy赋值操作来赋值给v。你可能会说,不返回对象就可以啊,可以返回一个引用或者一个指针,但是要这样做就得分配内存,但是C++的一个设计目标就是尽量少分配内存。上述更糟糕之处在于,返回的临时对象使用完之后是会被销毁掉的。

理论上来说,在内存中我们完全可以把临时对象的指针偷过来,而不去拷贝。还有就是我们为什么不能move呢?C++03说,我不知道哪个对象是临时的,哪个不是临时的,但是C++11却可以做到。

这就是右值引用和move语义要做的事情:move语义可以使你在使用临时对象时避免拷贝,并且可以安全的使用临时对象里的资源。

左值和右值

C++中,左值是指可以使用其地址的表达式,左值提供一种(半)永久的内存,比如:

int a;

a = 1;  //a是一个左值

又如:

  1. intx;
  2. int& getRef ()
  3. {
  4. returnx;
  5. }
getRef()
= 4;//getRef()这个表达式也是一个左值,因为其提供的返回值全局变量,是有固定地址的。
右值呢?如果一个表达式的结果在一个临时变量中,那它就是一个右值,例如:
  1. intx;
  2. intgetVal ()
  3. {
  4. returnx;
  5. }
  6. getVal();

再来看返回对象的情况:

  1. string getName ()
  2. {
  3. return"Alex";
  4. }
getName();
string
name = getName();

此时,对“Alex”进行隐式类型转换,转换为一个string的临时对象,然后拷贝构造给name变量

 

上边说到C++11能检测出来临时变量和非临时变量,现在就来看看怎么检测

既然有的表达式返回临时变量,那么如果对表达式重载,那么一个返回临时变量的表达式和返回非临时变量的表达式是否有哪些不同呢?

conststring&
name = getName();
//
ok

string&
name = getName();
//
NOT ok

为什么第一个ok第二个不ok呢,C++认为不管getName如何实现,如果你返回的是一个非const引用,说明你可能回去修改一个可能会消失

(如果是一个临时变量的话)东西;

另外,如果是返回一个const引用,确保了临时变量不会很快消失(不是很懂,不会很快消失,那多久会消失呢)。

C++11中,会让你可以绑定一个mutable右值引用,也就是说,一个值是否临时的,可以使用右值引用来检测,右值引用使用&&语法,可以是const也可以是非const的:

conststring&&
name = getName();
//
ok

string&&
name = getName();
//
also ok - praise be!

有什么用呢?关于左值引用和右值引用,最重要的是,当你写一个函数,函数的参数是左值引用或者右值引用时,比如:

[cpp] view plaincopy

  1. printReference (constString& str)
  2. {
  3. cout << str;
  4. }
  5. printReference (String&& str)
  6. {
  7. cout << str;
  8. }

第一个具有const引用的函数,可以接受任意的参数,不管是左值还是右值,不管这个左值或者右值易变或者不易变(if mutable);

但是对于第二个函数,除了mutable rvalue-references类型,其他的类型都可以,比如:

[cpp] view plaincopy

  1. string me( "alex");//mutable rvalue-references类型
  2. printReference(  me ); // calls the first printReference function, taking an lvalue reference

如果是printReference("alex");是不是就调用的第二个函数?

现在,右值引用版本的函数就像一个俱乐部的入口,只有临时变量才可以进入。

现在,既然有方法检测出是否临时变量了,有什么用处呢?

 
move构造和move赋值操作符
右值引用的一个用处是可以用来创建move构造函数和move赋值操作符,move构造和move赋值可以避免内存重新分配,
因为我们已经有了一个临时变量了。
把一个对象中的一个字段移动到另一个对象是什么意思?如果是一个原生类型,比如int,我们赋值就可以了,
但是对于指针类型的处理比较有趣。我们不需要再
分配和初始化一段内存,我们只需要把临时对象中的指针偷过来,然后让原始指针指向null即可,
因为临时对象不再需要了,所以我们可以把它的指针拿过来
用:
来看拷贝构造:
  1. class ArrayWrapper
  2. {
  3. public:
  4. ArrayWrapper (int n)
  5. : _p_vals( new int[ n ] )
  6. , _size( n )
  7. {}
  8. // copy constructor
  9. ArrayWrapper (const ArrayWrapper& other)
  10. : _p_vals( new int[ other._size  ] )
  11. , _size( other._size )
  12. {
  13. for ( int i = 0; i < _size; ++i )
  14. {
  15. _p_vals[ i ] = other._p_vals[ i ];
  16. }
  17. }
  18. ~ArrayWrapper ()
  19. {
  20. delete [] _p_vals;
  21. }
  22. private:
  23. int *_p_vals;
  24. int _size;
  25. };

来看move构造:

  1. class ArrayWrapper
  2. {
  3. public:
  4. // default constructor produces a moderately sized array
  5. ArrayWrapper ()
  6. : _p_vals( new int[ 64 ] )
  7. , _size( 64 )
  8. {}
  9. ArrayWrapper (int n)
  10. : _p_vals( new int[ n ] )
  11. , _size( n )
  12. {}
  13. // move constructor
  14. ArrayWrapper (ArrayWrapper&& other)
  15. : _p_vals( other._p_vals  )
  16. , _size( other._size )
  17. {
  18. other._p_vals = NULL;
  19. }
  20. // copy constructor
  21. ArrayWrapper (const ArrayWrapper& other)
  22. : _p_vals( new int[ other._size  ] )
  23. , _size( other._size )
  24. {
  25. for ( int i = 0; i < _size; ++i )
  26. {
  27. _p_vals[ i ] = other._p_vals[ i ];
  28. }
  29. }
  30. ~ArrayWrapper ()
  31. {
  32. delete [] _p_vals;
  33. }
  34. private:
  35. int *_p_vals;
  36. int _size;
  37. };

可以看出,move构造和copy构造相互重载了,那么调用的时候如何判断调用哪个构造呢?很简单,上边不是已经有了方法吗,

如果是临时对象就会自动调用move构造,如果不是,就会调用copy构造。
那么为什么move构造中要把原始指针置为null呢?因为如果不置为null,那么临时变量消失时,会析构,释放自己的指针,
还资源给os,那么新对象
中的指针也就随之会被delete掉。所以需要把原始指针置为null,这样临时变量在析构时就找不到给自己分配的那块内存了,
那块内存也就可以被新对象
正常使用了。

注意:这里的重载规则要求如果要调用move构造,那么一定要传一个临时变量,并且一定要是一个可以修改的临时变量

[转] C++11带来的move语义的更多相关文章

  1. C++11中的右值引用及move语义编程

    C++0x中加入了右值引用,和move函数.右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能修临时对象的内容,右 ...

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

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

  3. move语义和右值引用

    C++11支持move语义,用以避免非必要拷贝和临时对象. 具体内容见收藏中的“C++右值引用” .

  4. c++11 中的 move 与 forward

    [update: 关于左值右值的另一点总结,请参看这篇] 一. move 关于 lvaue 和 rvalue,在 c++11 以前存在一个有趣的现象:T&  指向 lvalue (左传引用), ...

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

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

  6. C++2.0新特性(五)——<Rvalue_reference和move语义>

    一.Rvalue_reference(右值引用)和move语义 1.左右值概念区分 左值:表达式结束后依然存在的对象,我们也叫做变量: 右值:表达式结束后就不存在的临时对象. 2.判断左值和右值 能对 ...

  7. C++11带来的优雅语法

    C++11带来的优雅语法 自动类型推导 auto auto的自动类型推导,用于从初始化表达式中推断出变量的数据类型.通过auto的自动类型推导,可以简化我们的编程工作; auto是在编译时对变量进行了 ...

  8. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part6:Move语义和编译器优化

    本文为第六部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.ht ...

  9. C++11中std::move的使用

    std::move is used to indicate that an object t may be "moved from", i.e. allowing the effi ...

随机推荐

  1. 绑定下拉框时避免触发SelectedIndexChanged事件

    在从数据库读取数据集绑定到下拉框时会立即触发其SelectedIndexChanged事件造成异常,可对其SelectedIndexChanged事件采取先解除后附加的方法解决. cmbXl_gt.V ...

  2. Array.prototype.map()

    mdn上解释的特别详细 概述 map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组. 语法 array.map(callback[, thisArg]) 参数 callb ...

  3. Joomla插件汉化小程序

    这两天在搞joomla插件,在看peter的视频,在此谢过他了.看到它汉化插件那个视频.反正闲着无聊,就写了一个Java小程序,方便使用joomla的人汉化插件.这个程序的方法很简单,你只要先运行ou ...

  4. 阿里云 centos vim 显示中文 乱码

    开始以为是vim 设置编码的问题 :网上搜  改 .vimrc    无效!!! 后转战  是不是系统里面没有中文字体 1.先从你本机 C:\Windows\Fonts 拷贝或者网络上下载你想要安装的 ...

  5. python unicode&str 转化

    从数据库中取出的值是Unicode编码的 需要转化为str才能正常使用 参考: http://www.mamicode.com/info-detail-308445.html

  6. TatukGIS-TGIS_LayerVector-LocateEx

    方法原型: function LocateEx(const _ptg: TGIS_Point; const _prec: Double; const _uid: Integer; var _dist: ...

  7. Linux系统管理技术手册——第6章 添加新用户

    6.1/etc/passwd文件 用户登录时Linux识别用户的文件/etc/passwd /etc/passwd包括7个字段: 登录名(不超过32位,使用NIS系统后不超过8位) 经过加密的口令或口 ...

  8. SVN版本回滚~

    如果你在svn上对文件进行编辑作了修改,想撤销,那么有两种方法可以还原:1) svn revert <yourfile>2) 手动删除该文件,重新执行svn up(rm <yourf ...

  9. Java中权限修饰符public、private、protected和default的区别

    1.public 可以修饰类.成员变量和成员函数,没有任何限制,同一个包中,或者不同包中的类都可以自由访问 2.private 可以修饰成员变量和成员函数,只能在本类中使用 3.default (不写 ...

  10. ios入门之c语言篇——基本函数——5——素数判断

    参数返回值解析: 参数: m:int,需要判断的值: 返回值: 0:非素数 1:素数 函数解析: 注意:函数没有对输入进行判断,请自己屏蔽非法输入 int prime(int m) { int tem ...