当定义一个新类型的时候,需要显式或隐式地指定复制、赋值和撤销该类型的对象时会发生什么——这是通过定义特殊成员:复制构造函数、赋值操作符和析构函数来达到的。如果没有显式定义复制构造函数或者赋值操作符,编译器(通常)会为我们定义。
看看编译器默认的构造函数和赋值操作符做了些什么工作:
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),这样就会导致意想不到的结果,这种情况下,我们需要定义一个该对象的指针而不是一个临时变量对象。
来源:oschina
链接:https://my.oschina.net/u/1034487/blog/124235