码迷,mamicode.com
首页 > 移动开发 > 详细

C++11之右值引用与移动构造

时间:2018-07-15 14:53:40      阅读:214      评论:0      收藏:0      [点我收藏+]

标签:not   ide   资源   mem   xxxx   size   int   移动语义   ati   

 

----------------------------右值引用---------------------------------

 

右值定义:

  通俗来讲,赋值号左边的就是左值,赋值号右边的就是右值。可以取地址是左值,不可以取地址的是右值。C++11,之前没有明确提出右值的概念,所以 C++11 以前这些说活都是正确的。

  C++11 中的左值,仍然等同于 C++98 左值。C++11 中的右值,除了 C++98 中的右值以外,增加了将亡值的。 


  如下图

技术分享图片

 

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    int a; int & lref = a;
    //int && rref = a;   //!error:右值引用不能接受左值
    //int & elref = a*34; //!error:普通引用不能接受临时值
        const int &eclref = a*34;
    int && erref = a*34;
    return 0;
}

 

 

右值引用主要解决什么问题?

  为什么要引入右值引用这个概念,其实就是为了解决临时对象带来的效率问题。比如,我们返回一个临时对象。

  在 C++中,栈对象是可以返回的,栈对象象的引用却不可以返回。

  栈对象返回,如果在没有优化的情况下 -fno-elide-constructors,会产生临时对象。
过程如下:

 

  在Qt的工程文件中,也就是pro中添加QMAKE_CXXFLAGS_DEBUG += -fno-elide-constructors

禁止Qt自动优化。

#include <iostream>
using namespace std;
class A
{
public:
    A(){
        cout<<"A() "<<this<<endl;
    }

    ~A(){
        cout<<"~A() "<<this<<endl;
    }
    A(const A &another)
    {
        cout<<"A(const A&)"<<&another<<"->"<<this<<endl;
    }

    void dis()
    {
        cout<<"xxxxoooooooooooo"<<endl;
    }
};
A getObjectA()
{
    return A();
}

int main(int argc, char *argv[])
{
  //先后注掉这两行代码得到执行1结果 和 执行2结果 A
a = getObjectA(); //!执行1
   A &&a = getObjectA();//!执行2
  return 0; 
}
执行1结果:
A() 0x61fe6f
A(const A&)0x61fe6f->0x61fe9f
~A() 0x61fe6f
A(const A&)0x61fe9f->0x61fe9e
~A() 0x61fe9f
~A() 0x61fe9e


执行2结果:
A() 0x61fe7f
A(const A&)0x61fe7f->0x61feab
~A() 0x61fe7f
~A() 0x61feab

执行1结果分析:

技术分享图片

匿名对象(栈空间)——>临时对象(寄存器)——>返回的对象(栈空间) :经历了两次拷贝

 

执行2结果分析:

技术分享图片

匿名对象(栈空间)——>返回的对象(栈空间);一次拷贝

 

 const T & 万能常引用

   其本质也是产生了临时对象, 并且该临时对象是 const 的。 此对于非基本数据类型也适用, 但需要转化构造函数。 

 

int main(int argc, char *argv[])
{
  // int& ri = 5;
  // const & cri = 5;
  // float f = 34.5;
  // int & irf = f;
  // const int & cirf = f;
  // A objA;
  // int& irA = objA;
  // const int& cirA = objA;
  // cout<<cirA<<endl;
  const A& ret = getObjectA();
  ret.dis(); //!error:一个const对象,没有const函数,不能再完成调用。
  return 0;
}

  此种举措,也可以在返回中避免临时对象,再次拷贝和销毁。但时临时对象的性质是 const 的,也会给后续的使用带来不便。
 

 右值引用与左值引用的对比
1) 都属于引用类型。
2)都必须初始化。 左值引用是具名变量值的别名, 右值引用是匿名变量的别名。
3) 左值引用, 用于传参, 好处在于, 扩展对象的作用域。 则右值引用的作用就在于延长了临时对象的生命周期。
4)  避免“先拷贝再废弃” 带来的性能浪费。
5)  对将亡值进行引用的类型; 它存在的目的就是为了实现移动语义

 

 

 const T & 与T && 本质对比

#include <iostream>

using namespace std;

int main( )
{
  const int & i =10;
 int && iii = 10 }

/*
int && iii = 10的汇编代码
0x08048400  mov    $0xa,%eax
0x08048405  mov    %eax,-0xc(%ebp)
0x08048408  lea    -0xc(%ebp),%eax
0x0804840b  mov    %eax,-0x4(%ebp)

第一句将10赋值给eax,第二句将eax放入-0xc(%ebp)处,“临时变量会引用关联到右值时,右值被存储到特定位置”,-0xc(%ebp)便是该临时变量的地址,后两句通过eax将该地址存到iii处。

通过上述代码,我们还可以发现,在上述的程序中-0x4(%ebp)存放着右值引用iii,-0x8(%ebp)存放着左值引用,-0xc(%ebp)存放着10,而-0x10(%ebp)存放着1,左值引用和右值引用同int一样是四个字节(因为都是地址)

const int & i =10的汇编代码
0x08048583  mov    $0xa,%eax
0x08048588  mov    %eax,-0x8(%ebp)
0x0804858b  lea    -0x8(%ebp),%eax
0x0804858e  mov    %eax,-0x4(%ebp)

-0x4(%ebp)处存放着i,-0x8(%ebp)处则存放着临时对象10,程序将10的地址存放到了i处。看到这里会发现const引用在绑定右值时和右值引用并没有什么区别。

*/

参考:https://www.cnblogs.com/likaiming/p/9045642.html

 

 

 

重要的事情再说一遍:  

  右值引用可以承接一个临时变量,并且将临时变量的生命周期扩展到右值引用所在的作用域。

右值引用存在的意义就在于既获得了同const T&同样的效率,又解决了const引用不可不可作文non-const的问题。

  C++中能够接受临时变量(右值)的只有两种类型,一种是const类型,另一种是右值引用。但是当使用const类型接收了一个类对象时,使用该对象是无法调用内部非const类型函数的。(const 对象只能调用const函数)。但是使用右值引用却是可以的。并且相对于直接使用对象来接收临时变量,使用右值引用。在系统层面只产生两次构造(包含一次拷贝构造)和两次析构。这在效率上来说是非常高的。

 

---------------------------------------移动构造--------------------------------------------

深拷贝深赋值

  对于类中,含有指针的情况,要自实现其拷贝构造和拷贝赋值。也就是所谓的深拷贝和深赋值。我想这己经成为一种共识了。

比如如下类:

#include <iostream>
using namespace std;
class HasPtrMem
{ 
public:
  HasPtrMem():_d(new int(0)){
    cout<<"HasPtrMem()"<<this<<endl;
  } 

HasPtrMem(
const HasPtrMem& another) :_d(new int(*another._d))
{   cout
<<"HasPtrMem(const HasPtrMem&   another)"<<this<<"->"<<&another<<endl; }

~HasPtrMem(){   delete _d;   cout<<"~HasPtrMem()"<<this<<endl;   }

  int
* _d; }; HasPtrMem getTemp() {   return HasPtrMem(); } int main(int argc, char *argv[]) {

// HasPtrMem a; // HasPtrMem b(a); // cout<<*a._d<<endl; // cout<<*b._d<<endl;   HasPtrMem&& ret = getTemp();   return 0; }

 

 

  上面的过程,我们己经知晓,ret 作为右值引用,引用了临时对象,由于临时对象是待返回对象的复本,所以表面上看起来是,待返回对象的作用域扩展了,生命周期也延长了。

 

从右值引到移动构造


  前面我们建立起来了一个概念,就是右值引用。用右值引用的思想,再来实现一下拷贝。这样,顺便把临时对象的问题也解决了。 

#include <iostream>
using namespace std;

class HasPtrMem
{ 
public:
    HasPtrMem():_d(new int(0)){
    cout<<"HasPtrMem()"<<this<<endl;
} 

HasPtrMem(const HasPtrMem& another)
:_d(new int(*another._d)){
    cout<<"HasPtrMem(const HasPtrMem& another)" <<this<<"->"<<   &another<<endl;
} 

HasPtrMem(HasPtrMem
&&another) { cout<<this<<" Move resourse from "<<&another<<"->"<< another._d <<endl; _d = another._d; another._d = nullptr; } ~HasPtrMem(){ delete _d; cout<<"~HasPtrMem()"<<this<<endl; } int * _d; }; HasPtrMem getTemp() { return HasPtrMem(); } int main(int argc, char *argv[]) { HasPtrMem a = getTemp(); return 0; }

 

移动构造

  如下是,移动构造函数。我们借用临时变量,将待返回对象的内容“偷”了过来。

  移动构造充分体现了右值引用的设计思想,通过移动构造我们也在对象层面看清了右值引用的本质。从而对于普通类型右值引用内部是怎样操作的的也就不难理解了。

//移动构造
HasPtrMem(HasPtrMem &&another) { cout<<this<<" Move resourse from "<<&another<<"->"<< another._d<<endl; _d = another._d; another._d = nullptr; }

   再来看一下拷贝构造函数,我们对比一下区别:

HasPtrMem(const HasPtrMem& another)
:_d(new int(*another._d)){
    cout<<"HasPtrMem(const HasPtrMem& another)" <<this<<"->"<<   &another<<endl;
} 

 

  移动构造相比于拷贝构造的区别,移动构造通过指针的赋值,在临时对象析构之前,及时的接管了临时对象在堆上的空间地址。

 

 

 

关于默认的移动构造函数

  对于不含有资源的对象来说,自实现拷贝与移动语义并没有意义,对于这样的类型 而言移动就是拷贝,拷贝就是移动。

  拷贝构造/赋值和移动构造/赋值,必须同时提供或是同时不提供。才能保证同时俱有拷贝和移动语义。只声明一种的话,类只能实现一种语义。

  只有拷贝语义的类,也就是 C++98 中的类。而只有移动语义的类,表明该类的变量所拥有的资源只能被移动,而不能被拷贝。那么这样的资源必须是唯一的。只有移动语义构造的类型往往是“资源型”的类型。比如智能指针,文件流等。
技术分享图片

 

 效率问题

 

 

#include <iostream>

using namesapce std;


class
Copyable { public: Copyable(int i) :_i(new int(i)) { cout<<"Copyable(int i):"<<this<<endl; } Copyable(const Copyable & another) :_i(new int(*another._i)) { cout<<"Copyable(const Copyable & another):"<<this<<endl; } Copyable(Copyable && another) { cout<<"Copyable(Copyable && another):"<<this<<endl; _i = another._i; } Copyable & operator=(const Copyable &another) { cout<<"Copyable & operator=(const Copyable &another):"<<this<<endl; if(this == & another) return *this; *_i=*another._i; return *this; } Copyable & operator=(Copyable && another) { cout<<"Moveable & operator=(Moveable && another):"<<this<<endl; if(this != &another) { *_i = *another._i; another._i = NULL; } return * this; } ~Copyable() { cout<<"~Copyable():"<<this<<endl; if(_i) delete _i; } void dis() { cout<<"class Copyable is called"<<endl; } void dis() const { cout<<"const class Copyable is called"<<endl; } private: int * _i; }; void putRRValue(Copyable && a) { cout<<"putRRValue(Copyable && a)"<<endl; a.dis(); } void putCLValue(const Copyable & a) { cout<<"putCRValue(Copyable & a)"<<endl; a.dis();//error! } //const T&和T&&重载同时存在先调用谁? void whichCall(const Copyable & a) { a.dis(); } void whichCall(Copyable && a) { a.dis(); } int main(int argc, char *argv[]) { // Copyable rrc = getCopyable(); cout<<"调用移动构造"<<endl; Copyable a =Copyable(2);//匿名对象/临时对象优先调用右值引用 构造-右值构造 cout<<"调拷贝构造"<<endl; Copyable ca(a); cout<<"直接构造右值"<<endl; Copyable && rra =Copyable(2); cout<<"=================================="<<endl; //右值引用与const引用。 效率是否一样? cout<<"右值引用传参"<<endl; putRRValue(Copyable(2)); cout<<"Const 引用传参"<<endl; putCLValue(Copyable(2)); cout<<"----------------------"<<endl; //优先调用哪种重载? T&& 还是 const T&? whichCall(Copyable(2)); //这个没什么好纠结的!T&&的出现就是了解决 const T &接受匿名/临时对象后,不能调用非cosnt函数的问题。 return 0; }

 

 

 

 

 

 

 

 

 

C++11之右值引用与移动构造

标签:not   ide   资源   mem   xxxx   size   int   移动语义   ati   

原文地址:https://www.cnblogs.com/wangkeqin/p/9302516.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!