定义一个空类会产生:
默认构造函数
析构函数
拷贝构造函数
赋值运算符(operator=)
取址运算符(operator&)(一对,一个非const的,一个const的)
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};
当然,所有这些只有当被需要才会产生。比如你定义了一个类,但从来定义过该类的对象,也没使用过该类型的函数参数,那么
基本啥也不会产生。在比如你从来没有进行过该类型对象之间的赋值,那么operator=不会被产生。
class Empty
{
public:
Empty(); // 缺省构造函数
Empty(const Empty&); // 拷贝构造函数
~Empty(); // 析构函数
Empty& perator=(const Empty&); // 赋值运算符
Empty* operator&(); // 取值运算符
const Empty* operator&() const; // 取值运算符
};
例如有以下class:
class StringBad public : } ; |
在构造函数和析构函数定义当中有如下定义:
StringBad::StringBad( const char * s) } StringBad::StringBad() } StringBad:: ~ StringBad() delete [] str; |
那么在程序当中如果有以下代码:
StringBad sports( " Spinach Leaves Bow1 for bollars " );
StringBad sailor = sports;
以上的第二条初始化语句将会调用什么构造函数?记住,这种形式的初始化等效于下面的语句:
StringBad sailor = StringBad(sports);
因为sports的类型为StringBad,因此相应的构造函数原型应该如下:
StringBad( const StringBad & );
当我们使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(称为复制构造函数,因为它创建对象的一
个副本)。
现在我们来看看我们没有定义复制构造函数的情况下调用隐式复制构造函数将会出现什么情况。
从构造函数定义的代码片断可以看到,当中使用new操作符初始化了一个指针str,而隐式的复制构造函数是按值进行复制
的,那么对于指针str,将会进行如下复制:
sailor.str = sports.str;
这里复制的不是字符串,而是一个指向字符串的指针!也就是说,我们将得到两个指向同一个字符串的指针!由此会产生的
问题将不言而喻。当其中一个对象调用了 析构函数之后,其str指向的内存将被释放,这个时候我们如果调用另一个对象,其
str指向的地址数据会是什么?很明显将会出现不可预料的结果。
所以由此可见,如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,
这被称为深度复制。因为默认的浅复制(或成为成员复制)仅浅浅的赋值指针信息。
我们再看以下代码片断,我们稍做修改:
StringBad headline1( " Celery Stalks at Midnight " );
StringBad knot;
knot = headline1;
这里的最后一行将与以上例子有所区别,现在是将已有对象赋给另一个已有对象,这将会采取其他操作,即使用重载的赋值
操作符。(我们需要知道的是:初始化总是会调用复制构造函数,而使用=操作符时也可能调用赋值操作符)因为C++允许对
象赋值,这是通过自动为类重载赋值操作符实现的。其原型如下:
Class_name & Class_name:: operator = ( const Class_name & );
它接受并返回一个指向类对象的引用。
与隐式的复制构造函数一样,隐式的对象赋值操作符也会产生同样的问题,即包含了使用new初始化的指针成员时,只会采
用浅复制。所以我们需要使用同样的解决办法,即定义一个重载的赋值操作符来实现深度复制。
所以综上所述,如果类中包含了使用new初始化的指针成员,我们应该显式定义一个复制构造函数和一个重载的赋值
操作符来实现其深度复制,避免由此带来的成员复制问题