[转] C++11带来的move语义

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

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

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

先看看这个例子:

[cpp] view plaincopy

  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是一个左值

又如:

[cpp] view plaincopy

  1. intx;
  2. int& getRef ()
  3. {
  4. returnx;
  5. }

getRef()
= 4;//getRef()这个表达式也是一个左值,因为其提供的返回值全局变量,是有固定地址的。

右值呢?如果一个表达式的结果在一个临时变量中,那它就是一个右值,例如:

[cpp] view plaincopy

  1. intx;
  2. intgetVal ()
  3. {
  4. returnx;
  5. }
  6. getVal();

再来看返回对象的情况:

[cpp] view plaincopy

  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即可,因为临时对象不再需要了,所以我们可以把它的指针拿过来
用:

来看拷贝构造:

[cpp] view plaincopy

  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构造:

[cpp] view plaincopy

  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构造,那么一定要传一个临时变量,并且一定要是一个可以修改的临时变量

时间: 2024-12-18 21:01:49

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

(译)C++11中的Move语义和右值引用

郑重声明:本文是笔者网上翻译原文,部分有做添加说明,所有权归原文作者! 地址:http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html C++一直致力于生成快速的程序.不幸的是,直到C++11之前,这里一直有一个降低C++程序速度的顽症:临时变量的创建.有时这些临时变量可以被编译器优化(例如返回值优化),但是这并不总是可行的,通常这会导致高昂的对象复制成本.我说的是怎么回事呢? 让我们

c++11 函数内部返回对象使用move语义的最佳实践

一句话,直接返回即可,不用任何变化. 当启动了c++11选项后,通过函数返回代码没有发生任何变化,但是已经使用了move语义,而不需要之前的NRVO编译器优化技术. 注意,右值引用rvalue reference是表达式计算完成后就不再存在的临时变量,左值是表达式计算完成后的变量.如果能够用&求址的,就是左值. 下面是stackoverflow上的一个讨论贴,比较有价值: 246down voteaccepted First example std::vector<int> retur

C++11之右值引用、move语义

C++11中增加了一个新的类型,即右值引用(R-value reference),标记为T&& .而它的目的就是去消除不必要的深拷贝,提高性能. 概念性的东西就不多说了.直接用代码体现其优势. 实现一个MyString类: 1 class MyString { 2 public: 3 MyString():m_data(nullptr), m_len(0) {} 4 MyString(const char* p) { 5 m_len = strlen(p); 6 copy_data(p);

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

C++0x中加入了右值引用,和move函数.右值引用出现之前我们只能用const引用来关联临时对象(右值)(造孽的VS可以用非const引用关联临时对象,请忽略VS),所以我们不能修临时对象的内容,右值引用的出现就让我们可以取得临时对象的控制权,终于可以修改临时对象了!而且书上说配合move函数,可以大大提高现有C++的效率.那么是怎样提高它的效率的呢?看段代码先! #include <iostream> #include <utility> #include <vector

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

本文为第四部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. 强制Move语义 众所周知,正如C++标准的第一修正案所陈述:“委员会不会建立任何试图绊住C++程序员的脚的规则.(The committee shall make no rule that prevents C++ programmers from shooting themselves in the foot.)”,正经来说,就是当面临给予程序员更多控制还是减

翻译「C++ Rvalue References Explained」C++右值引用详解 Part2:Move语义

本文为第二部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/4220233.html. Move语义 假设x是一个类,其含有一个指针或者某些资源的句柄(handle).写作m_pResource.由这个资源,我的意思是包括构造.克隆.析构都认真考虑在内的,一个绝佳的例子是std::vector.它可以在一个分配的内存数组中包含一个对象集合.接下来,从逻辑上,对于x的拷贝赋值操作符一般如下: X& X::operator=(X const & r

c++11 中的 move 与 forward

一. move 关于 lvaue 和 rvalue,在 c++11 以前存在一个有趣的现象:T&  指向 lvalue (左传引用), const T& 既可以指向 lvalue 也可以指向 rvalue.但却没有一种引用类型,可以限制为只指向 rvalue.这乍看起来好像也不是很大的问题,但其实不是这样,右值引用的缺失有时严重限制了我们在某些情况下,写出更高效的代码.举个粟子,假设我们有一个类,它包含了一些资源: class holder { public: holder() { res

C++ RVO/NRVO以及move语义的影响

C++返回值优化和具名返回值优化是编译器的优化,在大多数情况下能提高性能,但是却难以受程序员控制.C++11中加入了move语义的支持,由此对RVO和NRVO会造成一定影响.下面以一段代码来说明. RVO和NRVO在分别在copy/move construct,copy/move assignment八种简单情况,测试条件是g++ 4.8.2和clang++ 3.4,默认优化. #include <iostream> #include <vector> #include <s

深入右值引用,move语义和完美转发

深入右值引用,move语义和完美转发 转载请注明:http://blog.csdn.net/booirror/article/details/45057689 乍看起来,move语义使得你可以用廉价的move赋值替代昂贵的copy赋值,完美转发使得你可以将传来的任意参数转发给 其他函数,而右值引用使得move语义和完美转发成为可能.然而,慢慢地你发现这不那么简单,你发现std::move并没有move任何东西,完美转发也并不完美,而T&&也不一定就是右值引用-- move语义 最原始的左值