Sailor_forever [email protected] 转载请注明
http://blog.csdn.net/sailor_8318/archive/2008/10/31/3188958.aspx
46、每个有虚函数的类都有一个自己的VTABLE,画图解释继承链中的VTABLE关系
各种类型的对象具备和instrument相同的接口,因此可以响应相同的信息。因此其地址都可以放在instrument类型的指针数组中,但是编译器只知道其指向了一个instrument对象。
当创建含有虚函数的类或者从含有虚函数的类派生而来,编译器将为每个类创建唯一个VTABLE。在该表中包含了本类的或者从基类继承而来的虚函数的地址,当派生类中没有overriding基类中的虚函数时,将采用前一个基类的虚函数地址。当采用简单的继承机制时,每个对象只有一个VPTR,其由构造函数初始化指向该类的VTABLE。
当通过基类地址调用虚函数时,因为编译器并没有完整信息实现early binding,故其将插入代码获取虚函数地址。所有类在相同的位置即通常为第一个位置含有VPTR指针,因此编译器可以通过VPTR获得VTABLE,其中所有虚函数地址按照相同顺序排列。
47、虚函数调用的实质
Instrument * pi;
Brass B;
i = &B;
i->adjust(1);
写出最后一行的汇编代码:
28: Instrument* pi;
29: Brass B;
004010A 8 8D 4D F8 lea ecx,[ebp-8]
004010AB E8 73 FF FF FF call @ILT+30(Brass::Brass) (00401023)
30: pi=&B;
004010B0 8D 45 F 8 lea eax,[ebp-8]
004010B3 89 45 FC mov dword ptr [ebp-4],eax
31: pi->adjust(1);
004010B6 8B F4 mov esi,esp
004010B8 6A 01 push 1 ;参数入栈
004010BA 8B 4D FC mov ecx,dword ptr [ebp-4] ;this指针
004010BD 8B 11 mov edx,dword ptr [ecx] ;VPTR指针
004010BF 8B 4D FC mov ecx,dword ptr [ebp-4] ;作参数
004010C 2 FF 52 08 call dword ptr [edx+8] ;取虚函数
非静态成员函数的调用都涉及到将this指针传递给函数
48、什么是抽象基类,纯虚函数?
抽象基类可不可以有实例? 不能
纯虚函数可不可以有实现? 能
抽象基类中至少含有一个纯虚函数,其提供派生类的一些通用接口。
=0这告诉编译器在VTABLE中为该虚函数留一个位置,而不填充虚函数的地址。只要有一个成员函数被声明为纯虚函数,VTABLE表就是不完整的。当创建该类的对象时,编译器将给出错误信息告警。
也可以在基类中提供纯虚函数的实现,但是在派生类中仍然必须实现该纯虚函数,否则派生类变为纯虚函数。唯一的好处是在派生类中使用基类部分提供的该虚函数的公共代码
唯一的例外情况是,纯虚析构函数无需在派生类中重新实现
class X{
public:
virtual void f()=0 {
cout<<"haha"<<endl;
}
};
另外内联函数不能是虚函数,因为虚函数表需要显示的函数地址
49、在派生类中增加新的虚函数,能不能用基类的指针调用之,为什么?请从VTPR与VTABLE的角度来解释。
如果此时确切的知道基类指针指向的是派生类对象,需要调用该函数,该如何做?
不能调用基类中没有的虚函数。因为基类指针可以指向任意的派生类对象,有的含有该接口有些不含,为了确保正确,只能调用基类中已有的虚函数接口
通过该基类指针调用新增的虚函数,可以通过强制转换,但是其违背了虚函数的机制,因此只能采用RTTI动态类型识别机制了
class X{
public:
virtual void f(){cout<<"haha"<<endl;}
};
class Y :public X{
public:
virtual void f(){cout<<"hehe"<<endl;}
virtual void ff(){cout<<"hehehehe"<<endl;}
};
void main(){
X* p;
Y y;
p=&y;
Y* py=NULL;
py=static_cast<Y*>(p);
if(py) py->ff();
}
50、看下面的例子,谈谈为什么要避免值传递:
class X{
int xi;
public:
X(int i){xi=i;}
virtual int f() {return xi; }
};
class Y :public X{
int yj;
public:
Y(int i, int j):X(i),yj(j){}
virtual int f() {return X::f()+yj;}
};
void call(X B){cout<<B.f()<<endl;}
void main(){
Y A(5,8);
call(A);
}
OUTPUT:5
类对象在进行值传递时发生了“对象切片”,无论是默认的拷贝构造函数还是手工提供的,编译器都会在其中放一段代码对VPTR进行初始化。这里call的参数类型声明为基类,则调用了基类的拷贝构造函数,将对象的VTPR指向了基类的VTABLE(原本应该指向派生类的VTABLE的)。于是错误发生了。
回忆一下,什么方法可以迫使程序员放弃值传递? 私有拷贝构造函数
或者在基类中定义纯虚函数,则编译器将阻止进行值传递时,因为纯虚基类不能定义对象
51、无论是手工提供的构造函数还是默认的构造函数,无论是拷贝构造函数还是一般构造函数,编译器都会在其中加上一段代码用以初始化VPTR(当然是有虚函数的类的构造函数)
52、编译器插入到构造函数中的秘密代码一般有哪几部分?
a. 初始化VPTR(对于有虚函数的类来说)
b. 检查this指针(因为对象有可能没有构造成功,operator new)
c. 调用基类的构造函数(如果在派生类的初始化参数列表中没有显式的调用基类的构造函数,则编译器会自动调用基类的无参数构造函数(默认的或者手工的)即使派生类构造函数在函数体内部调用了基类构造函数也不例外,如果这时基类没有无参数构造函数(比如它只手工提供了带参数的构造函数)则编译出错)
class X{
public:
X() {cout<<"X::X()"<<endl;}
X(int i){cout<<"X::X(int)"<<endl;}
};
class Y :public X{
public:
Y(int i){X(5);}
};
void main(){
Y y(6);
}
OUTPUT:X::X()
X::X(int)
53、说明下例为什么无法编译通过?
class X{
public:
X(int i) {cout<<"X::X(int)"<<endl;}
};
class Y :public X{
public:
};
void main(){
Y y;
}
如何才能通过?
为Y自动生成无参构造函数,但X没有无参构造函数
54、上例中如果派生类改为:
class Y :public X{
public:
Y(){}
};
为何不能编译通过?如何修改?
规则:应该始终在初始化列表中调用基类构造函数。尽量避免调用默认的构造函数
55、如果在构造函数中调用了虚函数,只有虚函数的本地版本被调用。
class X{
public:
virtual void f(){cout<<"X::f"<<endl;}
X() {f();}
};
class Y : public X{
public:
virtual void f(){cout<<"Y::f"<<endl;}
Y() {f();}
};
class Z : public Y{
public:
virtual void f(){cout<<"Z::f"<<endl;}
Z() {f();}
};
void main(){
Z z;
}
OUTPUT:
X::f
Y::f
Z::f
Z的构造函数被调用时,编译器会先调用X的构造函数,然后是Y的,因为构造函数在真正运行之前会自动插入段代码,其将对象的VPTR指向自己类型的VTABLE,因此当构造到达X的构造函数体内时,VPTR是指向X的VTABLE的,因此调用的函数就是X版本的函数。
56、构造函数能不能是虚函数?
不能。
但试想用一个基类指针去析构一个派生类对象时会发生什么事情?如何解决?
若通过基类指针操作对象如delete,因为不知道基类指针指向的具体类型,因此安全万能的做法只能调用基类的析构函数。
令析构函数为虚函数,则通过基类指针析构时会自动调用派生类的析构函数。
析构函数不需要像构造函数那样显式的调用基类的析构函数,编译器会自动调用基类的构造函数。
57、若在析构函数中调用了虚函数,应该调用虚函数的哪个版本?
析构函数知道其自身由谁派生而来,但不知道谁会继承自己,因此只会调用自身和基类的析构函数。
其不会去处理一个也许几经被“析构”的派生类数据。因此晚捆绑机制在析构函数中被忽略。
59、多重继承应避免。当多重集成中出现菱形继承时,为了消除继承人中包含两个base类的实体,该如何?
虚拟继承
class base{};
class d1: virtual base{};
class d2: virtual base{};
class mi: public d1, public d2{};
60. RTTI。一般我们将一个指针向上映射为一个基类指针,然后通过虚函数使用基类的接口。但是偶尔我们需要知道一个基类指针指向对象的确切类型,于是有了RTTI。
注意,RTTI依赖与驻留在虚函数表中的类型信息,所以在一个没有虚函数的类上使用RTTI是会出错的。