C++ : 构造函数,拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符应用场景

  构造函数,拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符应用场景

#include <iostream>
using namespace std;

class ConstructTest{
public:
    ConstructTest(){
        cout<<"default Constructor\n";
        dim_=0;
        base_= nullptr;
    };   
    ~ConstructTest(){
        cout<<"Destructor:base "<<base_<<endl;
        if (base_ != nullptr){
            delete base_;
        }
    }
    ConstructTest(int dim){
        cout<<"Constructor with param"<<endl;
        dim_=dim;
        base_ = new int [dim];
        for (int i = 0; i < dim_; ++i) {
            *(base_ + i) = 0;
        }
    }
    ConstructTest (const ConstructTest & a){
        cout<<"copy Constructor"<<endl;
        dim_= a.dim_;
        base_ = new int [dim_];
        for (int i = 0; i < dim_; ++i) {
            *(base_ + i) = *(a.base_+i);
        }
    }
    ConstructTest& operator =(const ConstructTest & a){
        cout<<"copy assign "<<endl;
        dim_= a.dim_;
        base_ = new int [dim_];
        for (int i = 0; i < dim_; ++i) {
            *(base_ + i) = *(a.base_+i);
        }
        return *this;
    }
    ConstructTest& operator =( ConstructTest && a)noexcept{
        cout<<"moving copy assign "<<endl;
        //避免自己移动自己
        if ( this == &a )
            return *this;
        delete base_;
        dim_ = a.dim_;
        base_ = a.base_;
        a.base_ = nullptr;
        return *this;
    }
    ConstructTest (ConstructTest && a) noexcept{
        cout<<"moving copy Constructor"<<endl;
        dim_ = a.dim_;
        base_ = a.base_;
        a.base_ = nullptr;
    }

public:
    int  dim_;
    int * base_;
};
ConstructTest square(ConstructTest para){
    ConstructTest ret(para.dim_);
    ret.base_ = new int [para.dim_];
    for (int i = 0; i < para.dim_; ++i) {
        *(ret.base_+i) = *(para.base_+i) * (*(para.base_+i));
    }
    return  ret;
}

int main(){
    ConstructTest c1(3);
    ConstructTest c2(c1);
    ConstructTest c4 ;
    c4=c2;
    cout<<"------------------------\n";
    ConstructTest c5 ;
    c5=square(c4);
    ConstructTest c6 = std::move(c5);
    cout<<"------------------------\n";
    ConstructTest c7;
    c7=ConstructTest();
    ConstructTest c8 = square(c7);
    ConstructTest c9 = ConstructTest();
    cout<<"<<<<<<finish >>>>>>>>\n";
}

C++ 和JAVA不一样的是,C++ 区分了值类型和引用类型,不像JAVA一样全是引用类型。创建对象 JAVA 用  <类名> 对象名= new ......  而C++ 用 <类名> 对象名 就行 . 对于普通对象 用值传递的方式传到形参,有一个隐式赋值的过程,此时调用的拷贝构造函数.以下的构造函数使用场景:

构造函数 : 创建对象时,给对象初始化时调用。

拷贝(复制)构造函数: 利用相同的类对象给新对象初始化.时调用.

拷贝赋值运算符 : 两个旧对象之间的赋值。

所谓“移动”就是把a的内存资源挪为自用。

移动构造函数: 在创建对象时,用临时对象初始化时调用。在返回值传给返回值的副本时也会调用。

移动赋值运算符: 用临时对象给旧对象赋值时调用。

我用的是CLION,在CMakeList.txt 加入如下代码,来取消编译器优化

add_compile_options(-fno-elide-constructors)

在无编译器优化的情况下,输出结果:

Constructor with param
copy Constructor
default Constructor
copy assign
------------------------
default Constructor
copy Constructor
Constructor with param
moving copy Constructor
Destructor:base 0
moving copy assign
Destructor:base 0
Destructor:base 0x3e1da8
------------------------
moving copy Constructor
------------------------
default Constructor
default Constructor
moving copy assign
Destructor:base 0
copy Constructor
Constructor with param
moving copy Constructor
Destructor:base 0
moving copy Constructor
Destructor:base 0
Destructor:base 0x3e1da8
default Constructor
moving copy Constructor
Destructor:base 0
<<<<<<finish >>>>>>>>
Destructor:base 0
Destructor:base 0x3e1918
Destructor:base 0
Destructor:base 0x3e1dd8
Destructor:base 0
Destructor:base 0x3e1d90
Destructor:base 0x3e1d78
Destructor:base 0x3e1d60

Process finished with exit code 0

在main函数第一到第四行中,展示了构造函数,拷贝构造,和拷贝赋值。

从第5行起:

 cout<<"------------------------\n";
    ConstructTest c5 ;
    c5=square(c4);
    ConstructTest c6 = std::move(c5);
    cout<<"------------------------\n";

首先用 默认构造函数创建对象c5,

在c5 = square(c4) 中, 首先把实参c4赋值给square的形参param,此时用的是拷贝构造函数。

在square函数体中,创建了局部对象ret,此时调用构造函数,

然后为返回值ret创建副本,此时调用移动构造函数,接着函数体结束,把形参param析构,

然后把原来ret的副本赋值给c5 ,对过程是对已经存在的对象c5进行赋值,所以调用移动赋值。

接着把ret 和ret的副本给析构。

利用 std::move() 手动把c5的内容移动给某个临时引用,把临时引用初始化给c6,调用移动构造函数.

    cout<<"------------------------\n";
    ConstructTest c7;
    c7=ConstructTest();
    ConstructTest c8 = square(c7);
    ConstructTest c9 = ConstructTest();
    cout<<"<<<<<<finish >>>>>>>>\n";

先默认构造函数创建c7对象。

用默认构造函数创建临时对象,把临时对象"移动"给c7;移动完后把临时对象析构

在c8 = square(c7) 中, 首先把实参c7赋值给square的形参param,此时用的是拷贝构造函数。

在square函数体中,创建了局部对象ret,此时调用构造函数,

然后为返回值ret创建副本,此时调用移动构造函数,接着函数体结束,把形参param析构,

然后把原来ret的副本赋值给c8 ,对还未创建的对象的对象c8进行初始化,所以调用移动构造

接着把ret 和ret的副本给析构。

创建临时对象,并用给c9进行初始化,最后把临时对象析构掉;

程序结束,析构所有变量。

编程思路

    在类成员变量带有指针的情况下,会面临如何施放资源的难题。应为默认拷贝构造,拷贝赋值,移动构造、移动赋值只是单单的浅拷贝。即值复制 指针指向内存的地址。在浅拷贝后,就会有两个对象中的成员指针指向同一处内存空间,如果在析构函数中对成员指针delete,最后就会面临对同一处内存空间施放两次。在C++中,如果访问不属于本程序中的内存就会出现“段错误”,即指针越界. 

    在编程时,可以利用 boost::shared_ptr 来管理动态分配的内存,此时可以不用理睬资源施放问题。因为智能指针能够对动态内存的引用计数归零时自动清理内存空间。

   如果要用 普通指针来管理动态内存,那么就要考虑施放资源,浅拷贝与深拷贝的问题。如果想用深拷贝那么就要,写好构造函数,拷贝构造函数,拷贝赋值运算符,移动构造,移动赋值运算符;

  在构造函数中初始化所有成员变量包括指针;在拷贝构造和拷贝赋值中重新为指针分配内存空间,并把对应的内存值进行赋值;在移动构造和移动赋值中,把返回内容复制完后要把临时对象指针内容重新赋空,达到不拷贝内存又不会施放两次内存的目的,即所谓的"移动“.

猜你喜欢

转载自blog.csdn.net/superSmart_Dong/article/details/108178633