继承中的复制控制注意事项
一、复制构造函数。
class derived_class :public base_class{ public: //constructor:
derived(const derived_class& d){ base(d); //使用派生类对象来初始化派生类的基类部分 } ...}; 如果没有在派生类的复制构造函数中显示地调用基类的复制构造函数,则会调用基类的默认构造函数来初始化基类。这样会产生一个奇怪的效果:派生类的自有部分是传进来的实参类的副本,而它的base部分则是默认值!!!
二、赋值操作符重载
同复制构造函数相同,如果派生类重载了赋值操作符,则必须显式调用基类的重载赋值操作符函数。需要注意的是,在赋值操作符重载中,一定要防止自身赋值。原因如下:
赋值操作符的实现需要以下步骤:
1、delete左值,释放内存。
2、new一块新的内存,以存放新内容。
3、将传入实参内容,放置到第二部new出来的空间中。
如果出现自身赋值,待传入的实参在第一步中已经被释放掉,导致出错!防止自身赋值的方法一般如下:
derived_class& derived_class::opreator=(const derived_class& rhs){
if(this!=&rhs) //判断传入实参的地址是否就是this指针的地址,即判断是否自身赋值
{
base_class::operator=(rhs);
...
}
return *this;
}
三、派生类析构函数
构造函数不能设为虚函数,但是,析构函数能够而且常常被设为虚函数!
因为派生类的析构函数不负责撤销基类对象的成员,所以在派生类的析构函数中要隐式地调用基类的析构函数(注:中文版的《C++ primer 4th》中错误的写为“显式的”,看技术类的书籍还是要原版啊!)
对于在堆中使用new动态分配的对象,删除其指针时,首先按照outside-in的层次结构调用各层次析构函数,最后释放掉指针指向的内存。
//VirtualDestructors.cpp // Behavior of virtual vs. non-virtual destructor #include <iostream> using namespace std; class Base1 { public: ~Base1() { cout << "~Base1()\n"; } }; class Derived1 : public Base1 { public: ~Derived1() { cout << "~Derived1()\n"; } }; class Base2 { public: virtual ~Base2() { cout << "~Base2()\n"; } }; class Derived2 : public Base2 { public: ~Derived2() { cout << "~Derived2()\n"; } }; int main() { Base1* bp = new Derived1; // Upcast delete bp; Base2* b2p = new Derived2; // Upcast delete b2p; } ///:~
运行结果会是:delete bp时,仅仅调用了Base1类的析构函数;而delete b2p时,先调用了派生类的析构函数,随后调用了Base2的析构函数。很显然,后者才是我们想要的结果,而前者会出现内存泄漏。
纯虚的析构函数
在Standard C++中,纯虚的析构函数是合法的。但是有一条额外的规定:必须要为纯虚的析构函数定义一个函数体。这听起来非常奇怪:
既然是一个纯虚函数,为什么需要一个函数体呢?!
原因是:由于虚析构函数的调用是由最外层的派生类一直调用的最内层基类的。如果最原始的基类没有一个函数体,那最后的析构函数将无从调用。所以,为纯虚析构函数设定一个函数体是必要的。事实上,这也是编译器所强制要求的。但是,要注意,纯虚析构函数是一个特例,在继承类中,并不需要去实现这个基类中的纯虚析构函数。编译器会提供一个合成版本的析构函数。那么,既然纯虚析构函数也不需要在派生类中对其定义,那么纯虚析构函数和普通的虚析构函数有什么区别呢?区别只有一个:
纯虚的析构函数可以防止对基类的实例化。
下面这段代码是可以成功运行的,虽然基类中有纯虚的析构函数,并且在派生类中没有定义析构函数!
#include <iostream>
using namespace std;
class Pet {
public:
virtual ~Pet() = 0;
}; //definition of base class
Pet::~Pet() {
cout << "~Pet()" << endl;
}
class Dog : public Pet {
public:
~Dog() {
cout << "~Dog()" << endl;
}
}; //definition of derived class
int main() {
Pet* p = new Dog; // Upcast
delete p; // Virtual destructor can succesfully called!
} ///:~