假设你写了一个不包含任何内容的空类:
class Empty{};
但是在C++看来,你是这样写的:
class Empty{
public:
Empty(){...} //default构造函数
~Empty(){...} //析构函数
Empty(const Empty& p){...} //copy构造函数
Empty& operator=(const Empty& p){...} //copy assignment操作符
};
这是因为如果你没有自己声明,那么经过C++处理过后,编译器就会为它声明一个copy构造函数、一个析构函数和一个copy assignment操作符。当这些函数被调用时,它们会被编译器创建出来:
Empty e1; //default构造函数
Empty e2(e1); //copy构造函数
e2 = e1; //copy assignment操作符
编译器在default函数和析构函数中调用base classes和非静态成员变量的构造函数和析构函数。至于copy构造函数,编译器只是简单地将来自于对象的每一个非静态成员变量拷贝到目标对象。
class Person{
public:
Person(string NAME, int AGE){...}
private:
string name;
int age;
};
在Person类中,由于你已经声明了一个构造函数,编译器就不会为它创建default构造函数。但是Person类中没有copy构造函数和copy assignment操作符,所以编译器会为其创建那些函数(如果被调用)。下面示范copy构造函数的用法:
Person p1("p1", 1);
Person p2(p1); //调用copy构造函数
在编译器生成的构造函数中,会以p1.name和p1.age的值来设定p2.name和p2.age。由于name是string类型,标准string有自己的copy构造函数,所以p2.name的初始化方式是调用string的copy构造函数并以p1.name为实参。而age的类型是int,是内置类型,所以p2.age会拷贝p1.age中的每一个bit来完成初始化。有3种情况会用到copy构造函数:
(1)一个函数以值传递的方式进入函数体
(2)一个对象以值传递的方式从函数返回
(3)一个对象需要另一个对象初始化
还有一点要提的是浅拷贝与深拷贝。
在没有定义copy构造函数时,系统会使用默认的copy构造函数,也就是浅拷贝,来完成对象中成员变量的一一赋值。如果成员变量中不包含指针,那么浅拷贝是可行的。但是当含有指针时,采用浅拷贝就会出现问题。在这种情况下,两个对象中的指针指向同一空间,如果一个对象析构,那么其指针指向的空间也会释放,而另一个指针会指向一个已经释放的空间,就会导致指针悬挂现象,这是错误的。因此当我们面对这种情况时,我们要使用深拷贝。深拷贝与浅拷贝的区别在于深拷贝会申请空间来存储数据,从而避免了指针悬挂。因此,当成员变量中包含指针时,我们要使用深拷贝。
至于Person类的copy assignment操作符,它的行为和copy构造函数如出一辙,但是是仅当代码合法的情况下。如果我们将name的类型改为string&,将int的类型改为const int,那么编译器会如何工作呢?答案是明显的,由于reference以及const都不允许被改动,所以编译器将拒绝这一赋值动作。还有一种情况就是base classes将copy assignment操作符定义为private,那么编译器会拒绝为derived classes生成copy assignment操作符。因为编译器生成的copy assignment操作符想象中可以处理base classes成分,但它们当然无法调用derived classes无法调用的函数,编译器对此无能为力。
如果你想要阻止编译器的copying行为,你需要明确的拒绝编译器。但这并不是要你不声明copy构造函数或copy assignment操作符,上面已经说过,编译器会为你自动生成一份。这似乎有一点难办,但是当你意识到编译器生成的函数都是public时,我们似乎找到了一种办法。我们要自行声明copy构造函数和copy assignment操作符,但是并没有什么需求将其声明为public,因此将其声明为private,从而阻止了编译器创建其专属版本的函数,而令这些函数为private也阻止了人们调用它。讲到这里,细心的你可能会发现这不安全,因为成员函数或友元函数还是可以调用它。为了解决这一问题,我们只需要不去实现它们即可。也就是将copy构造函数和copy assignment操作符声明为private,但是不定义它们。这样一来,如果被某些人不慎调用,就会获得一个连接错误,问题得到解决!
还有一种方法,就是在base class中阻止copying行为。
class Uncopyable{
protected:
Uncopyable(){} //允许derived class构造以及析构
~Uncopyable(){}
private: //阻止copying
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
为了阻止Empty对象被拷贝,我们唯一要做的就是继承Uncopyable:
class Empty: private Uncopyable{
......
};
这种做法是可行的,因为只要有人调用它,哪怕是成员函数或是友元函数,编译器便会生成copy构造函数或copy assignment操作符,然后编译器生成的这些函数便会调用其base class的对应兄弟,这时编译器会被拒绝,因为base class的拷贝函数是private。