zoukankan      html  css  js  c++  java
  • 拷贝构造函数与移动构造函数

    一、拷贝构造函数

    当类没有定义拷贝构造函数的时候,编译器会默认提供一个,这个拷贝函数是浅拷贝

    如果该类中含有指针,可能会发生内存泄漏,见下面的例子:

    class Test
    {
     public:
     int *p;
      Test(){ p=new int; };
      ~Test(){ delete p; };
    };
    void main()
    {
        Test t1;
        Test t2(t1);
        Test t3 = t1;
    }

    t1、t2、t3的成员变量p指向的是同一块内存,程序结束后会出现重复释放的问题。

    为了解决这个问题,可以自定义拷贝构造函数:

    class Test
    {
     public:
     int *p;
     Test(const Test &t)
     {
         p = new int (*(t.p));
     }
      Test(){ p=new int; };
      ~Test(){ delete p; };
    };

    二、右值引用

    除了上述的解决方法,还可以使用C++11的【右值引用】新特性来解决,而且可以提高程序的性能,减少内存开销。

    为了引出左值引用的概念,先来复习左值和右值

    1.左值和右值

    int a = 3 + 4 ;

    上面的式子中,变量 a 就是左值,右边的表达式会生成一个临时变量存放 (3+4) 的值,这个变量称之为右值。

    有两种方式可以判断:

    (1)只能放在等号(=)右侧的即为右值,可以放在左侧的为左值

    int a = 10 ;
    10 = a ; //错误

    (2)左值可以取地址,而右值不允许:

    int a = 3 + 4 ;
    int * b = & a ;  //ok
    b = & (3+4) ; //错误

    2.右值引用

     使用方法如下,b就是对右值 (3+4) 的引用。

    int && b = 3 + 4 ;

    先看下下面的左值引用:

    int a = 0 ;
    int &b = 4 ; //错误!
    int &b = a ; //左值引用

    如上例所示,左值引用只能对左值进行别名引用,无法引用右值

    于是C++11增加了右值引用,使用 && 表示(和逻辑运算中的”且“一致)。

    int a = 0 ;
    int b = 1 ;
    int && c = a+c ; //右值引用
    int && c = 3 ; //右值引用
    int && c = 3 +4 ; //右值引用
    int && c = a ; //错误!

    注意不能直接右值引用左值,C++提供了一个函数std::move()函数,可以将左值变成右值:

    string str1 = "aa" ;
    string && str2 = std::move( str1 );  //ok

    3.右值引用的应用场景

    (1)案例:

    还是回到之前的例子:

    class Test
    {
     public:
     int *p;
     Test(const Test &t)
     {
         p = new int (*(t.p)); cout<<"copy construct"<<endl;
     }
      Test(){ p=new int; cout<<"construct"<<endl; };
      ~Test(){ delete p; cout<<"destruct"<<endl; };
    };
    
    Test getTest()
    {
        return Test();
    }
    
    void main()
    {
        {
            Test t = getTest();
        }
    }

    使用vs2012运行,结果为:

    construct                 //执行 Test()
    destruct                  //销毁 t

     但需要注意的是,这是vs编译器对拷贝构造函数优化后的结果。禁止优化,结果为:

    construct                 //执行 Test()
    copy construct            //执行 return Test()
    destruct                  //销毁 Test() 产生的匿名对象
    copy construct            //执行 t = getTest()
    destruct                  //销毁 getTest() 返回的临时对象
    destruct                  //销毁 t

    可以看到,进行了两次的深拷贝,对于对内存要求不高、本例这种占内存比较小的类Test而言(申请的堆空间小),可以接受。

    但如果临时对象中的指针成员申请了大量的堆空间,那将严重影响程序的执行效率。

    C++11为了解决这一问题(深拷贝占用大量空间),引入移动构造函数

     (2)移动构造函数

    所谓的移动,就是将其他的内存资源,“移为己有”,这些资源通常是临时对象,比如上文所叙的右值

    修改如下(增加一个移动构造函数):

    class Test
    {
     public:
     int *p;
     Test(Test &&t) //移动构造函数
     {
         p = t.p;
         t.p = nullptr;//将临时对象的指针赋值为空
         cout<<"copy construct"<<endl;
     }
     Test(const Test &t) //拷贝构造函数
     {
         p = new int (*(t.p));
         cout<<"move construct"<<endl;
     }
      Test(){ p=new int; cout<<"construct"<<endl; };
      ~Test(){ delete p; cout<<"disconstruct"<<endl; };
    };
    Test getTest()
    {
        return Test();
    }
    void main()
    {
        {
            Test t = getTest();
        }
    }

    禁止vs优化,结果为:

    construct                 //执行 Test()
    move construct            //执行 return Test()
    destruct                  //销毁 Test() 产生的匿名对象
    move construct            //执行 t = getTest()
    destruct                  //销毁 getTest() 返回的临时对象
    destruct                  //销毁 t

    可以看到,定义了移动构造函数后,临时对象的创建使用移动构造函数创建,如下,没有在堆上创建对象,减少了开销。

     Test(Test &&t) //移动构造函数
     {
         p = t.p;
         t.p = nullptr;//将临时对象的指针赋值为空
         cout<<"copy construct"<<endl;
     }

    那么问题来了,什么时候调用移动构造函数,什么时候调用拷贝构造函数呢?将在后面的文章中分析。

  • 相关阅读:
    VS2015 C#取消最大化按钮,设置鼠标不可调整窗体大小
    C++调用C#编写的DLL【转】
    C#封装成DLL,并在C#中调用
    ubuntu永久修改主机名
    Anaconda3的安装和汉化
    windows下面安装Python和pip教程
    pip install bs4安装失败
    Sublime text 3中文汉化教程
    Vmware安装ubuntu详细教程
    Sublime text3修改tab键为缩进为四个空格
  • 原文地址:https://www.cnblogs.com/hosseini/p/15089358.html
Copyright © 2011-2022 走看看