C++复制构造函数以及赋值操作符

守給你的承諾、 提交于 2019-12-10 04:12:49

当定义一个新类型的时候,需要显式或隐式地指定复制、赋值和撤销该类型的对象时会发生什么——这是通过定义特殊成员:复制构造函数、赋值操作符和析构函数来达到的。如果没有显式定义复制构造函数或者赋值操作符,编译器(通常)会为我们定义。

看看编译器默认的构造函数和赋值操作符做了些什么工作:

class_a.h

#ifndef _CLASS_A_H
#define _CLASS_A_H
class A { 
public:
        A();    
        A(int a); 
private:
        int a;  
};
#endif

class_a.cpp

#include "class_a.h"
A::A(): a(0) 
{
} 
A::A(int a)
{
        this->a = a;
}

main.cpp

#include "class_a.h"

int main()
{
        A obj1;         //调用无形参构造函数
        A obj2(2);      //形参为int的构造函数
        A obj3(obj1);   //默认复制构造函数
        A obj4 = 3;     //调用形参为int的构造函数产生类A的临时变量,调用默认赋值操作符进行赋值
        A obj5 = obj1;  //调用默认赋值操作符

        return 0;
}

编译执行g++ -g main.cpp class_a.cpp
反汇编objdump -S a.out
可知:
默认复制构造函数执行的实质将obj1的内存复制到obj3的内存上
默认复制操作符当形参为非A&时,调用相应形参的构造函数(编译器层面上并没有生成临时变量的内存,应该是编译器优化掉了;生成临时变量是C++语法层面上的);否则直接将对象的内存复制到新对象上

原来默认的复制构造函数以及默认的赋值操作符只是复制sizeof(A)大小的内存复制给新的对象,或者是调用了相应的单形参的的构造函数;这也就是我们常说的浅拷贝,当我们的类中有指针或者引用时,一定要显式的定义复制构造函数以及赋值操作符。

我们显式的定义了复制构造函数以及默认赋值操作符:

class_a.h

#ifndef _CLASS_A_H
#define _CLASS_A_H
class A { 
public:
        A();    
        A(int a); 
        A(const A& obj_a); 
        A& operator=(const A& obj_a); 
        ~A();   
private:
        int a;  
};
#endif

class_a.cpp

#include "class_a.h"

A::A(): a(0)
{
} 

A::A(int a)
{
        this->a = a;
}

A::A(const A& obj_a)
{
        this->a = obj_a.a;
}

A& A::operator=(const A& obj_a)
{
        this->a = obj_a.a;
        return *this;
}

A::~A()
{
}

main.cpp

#include "class_a.h"

int main()
{
        A obj1;           //调用无形参构造函数
        A obj2(2);        //调用形参为int的构造函数
        A obj3(obj1);     //调用复制构造函数
        A obj4 = 3;       //调用形参为int的构造函数产生临时变量(若设为explicit编译不过)
                          //调用复制操作符进行赋值
        A obj5 = A(4);    //调用形参为int的构造函数产生临时变量(若设为explicit编译照样通过)
                          //调用复制操作符进行赋值
        A obj6 = obj1;    //调用复制构造函数产生临时变量,调用复制操作符进行赋值
        A obj7 = A(obj1); //调用复制构造函数产生临时变量,调用复制操作符进行赋值

        return 0;
}

编译 g++ -g main.cpp class_a.cpp
反汇编objdump -S a.out
可知:
obj4并没有产生临时变量,生成过程跟obj2一样,调用形参为int的构造函数
obj5也并没有产生临时变量,生成过程跟obj2一样,调用形参为int的构造函数
obj6也没有产生临时变量,直接调用自定义复制构造函数
obj7也没有产生临时变量,也是直接调用自定义复制构造函数

看来产生临时变量是C++语法上的,在编译器上进行了优化,否则产生临时变量的效率比较低,先要调用构造函数,然后还要调用析构函数进行析构,而且上述代码也只是调用了7次构造函数,验证了在编译器上没有产生临时变量。

C++ primer说:因为用于向函数产地对象和从函数返回对象,该构造函数一般不应该设置为explicit

将上述代码的复制构造函数设置成explicit,编译报错:obj4, obj5, obj7 :no matching function for call to ‘A::A(A)’obj6: no matching function for call to ‘A::A(&A)’,可以猜测C++语法上的临时变量是通过A::A(A)和A::A(A&)产生的,因为没有定义A::A(A)的构造函数,而A::A(A&)被定义成了explicit抑制了临时变量的产生。

复制构造函数以及赋值操作符的自定义,可以解决浅拷贝的问题;不会导致由于对象的析构中释放new对象导致其他对象的影响。
但是我们还是得注意另一个问题,当对象中带有操作系统进程级别的一些参数时(比如fd),在函数调用的时候不能在函数内部定义一个临时变量,并将对象赋值给这个临时变量,因为临时变量析构时就会调用自定义的close(fd),这样就会导致意想不到的结果,这种情况下,我们需要定义一个该对象的指针而不是一个临时变量对象。




易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!