构造函数
要了解这个话题,首先我们给出一段代码:
#include<iostream>
using namespace std;
class Animal{
int x;
public:
Animal(int x){
this->x = x;
}
virtual void eat(){
cout<<"eat"<<endl;
}
};
class Herbivore:public Animal{
int y;
public:
Herbivore(int x, int y):Animal(x){
this->y = y;
}
void eat(){
cout<<"eat grass"<<endl;
}
};
void print(Animal &a){
int num = sizeof(a)/ sizeof(int);
int *p = (int*)&a;
while(num){
printf("%x\n",*p);
++p;
--num;
}
};
int main(){
Animal a(1);
Herbivore h(1,2);
cout<<"Show the Animal Object...\n";
print(a);
cout<<"Show the Herbivore object...\n";
print(h);
system("pause");
return 0;
}
运行结果如下:
包含虚函数的对象有以下的结构:
当基类中的数据成员是private类型的,继承类的对象无法直接访问数据成员,但是它又必须创建所有的数据对象。从上面的对象的结构,我们也发现,基类对象在上,继承类的对象依次在下面。这也表明了如下的结论:
构造函数的调用次序是沿着类的生成过程进行的。因为只有保证了底层的正确,才能进一步构造。这样的生成顺序也是为了vptr的设置考虑的。因最终的vptr是most-derived的那个类的virtual table的地址,所以,从下往上的生成过程会用上层的vptr不断覆盖底层的,使得最终得到正确的vptr.
如果在构造函数中调用虚函数会和在普通函数中有和区别呢?
众所周知,构造函数是产生对象的函数,如果在构造函数中调用虚函数,那么只能调用base类中的虚函数,并且在前面加上相应的namespace.如果什么都不加,则默认是该对象中的虚函数。而普通成员函数中调用虚函数,则很有可能是一个继承当前类的一个类中的成员函数。
下面用程序来进行说明:
#include<iostream>
using namespace std;
class Animal{
int x;
public:
Animal(int x){
this->x = x;
}
virtual void eat(){
cout<<"eat "<<x<<endl;
}
void description(){
eat();
}
};
class Herbivore:public Animal{
int y;
public:
Herbivore(int x, int y):Animal(x){
this->y = y;
}
void eat(){
cout<<"eat grass"<<endl;
cout<<y<<endl;
}
};
int main(){
Herbivore h(1,2);
h.description();
system("pause");
return 0;
}
Animal类中的成员函数description有一个引用类型的参数,并且,该参数调用了一个虚函数,因此需要在运行时决定其类型。
析构函数
构造函数是不能被声明为虚函数的。因为构造函数是piece-by-piece的模式来构建对象的。那么析构函数可以么?
下面来看一个例子:
#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"<<endl;
}
};
int main(){
Base1 *bp = new Derived1;
delete bp;
Base2 *b2p = new Derived2;
delete b2p;
system("pause");
return 0;
}
运行结果如下:
从运行结果可得出下面的结论:
如果将析构函数声明为虚函数的话,可以保证按照从下往上的调用次序进行析构;否则,就只调用基类的析构函数。所以,将析构函数设置为虚函数则是必要的。
产生这种现象的原因是,当用基类的指针或引用来操作派生类的对象时,如果析构函数不设置为虚函数,那么它对于基类来说是不可见的,因此无法正确的调用。
至于链式的析构,则是在析构函数内部实现的。和构造函数的思想类似。也是唯一使用这种链式机制的两类函数。
reference
thinking in c++