• 拷贝构造函数与移动构造函数


    一、拷贝构造函数

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

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

    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;
     }

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

  • 相关阅读:
    RK3288 Android5.1系统编译
    RK3288 模块单独编译
    ubuntu 设置固定DNS
    PHP取整函数之ceil,floor,round,intval的区别
    PHP实现 APP端微信支付功能
    Laravel中服务提供者和门面模式
    laravel项目利用twemproxy部署redis集群的完整步骤
    PHP转盘抽奖算法
    laravel中短信发送验证码的实现方法
    PHP操作Redis常用
  • 原文地址:https://www.cnblogs.com/hosseini/p/15089358.html
Copyright © 2020-2023  润新知