抽象基类
c++源码如下:
class X {
private:
int i;
public:
virtual void f() = 0;//纯虚函数
X() {
i = 1;
}
};
class Y : public X {//Y继承自X
private:
int j;
public:
virtual void f() {
j = 2;
}
};
int main() {
Y y;
}
只看父类X的构造函数和子类Y的构造函数的汇编码:
子类Y构造函数的汇编码:
??0Y@@QAE@XZ PROC ; Y::Y, COMDAT
; _this$ = ecx
push ebp
mov ebp, esp
push ecx;为保存子对象首地址预留空间
mov DWORD PTR _this$[ebp], ecx;将ecx(里面存放子对象首地址)的值放到刚才的空间
mov ecx, DWORD PTR _this$[ebp];将子对象首地址传给ecx,作为隐含参数(this指针)调用父对象的构造函数
call ??0X@@QAE@XZ ; 调用父对象的构造函数
mov eax, DWORD PTR _this$[ebp];将子对象首地址给eax t
mov DWORD PTR [eax], OFFSET ??_7Y@@6B@;将子对象的vtable首地址存到子对象首地址所指向的内存,即初始化子对象的vptr
mov eax, DWORD PTR _this$[ebp];将子对象的首地址给eax,作为返回值。构造函数总是返回对象首地址
mov esp, ebp
pop ebp
ret 0
??0Y@@QAE@XZ ENDP
父类X构造函数汇编码:
??0X@@QAE@XZ PROC ; X::X, COMDAT
; _this$ = ecx
; 6 : X() {
push ebp
mov ebp, esp
push ecx;压栈的目的就是为存储父对象首地址(即this指针)预留空间
mov DWORD PTR _this$[ebp], ecx;将父对象首地址存到刚才的空间
mov eax, DWORD PTR _this$[ebp];将父对象的首地址传给eax
mov DWORD PTR [eax], OFFSET ??_7X@@6B@;将父对象的vtable(由于父类为抽象类,其vtable不完全,即里面没有存放纯虚函数的地址,只为其保留了一个位置)首地址存到父对象首地址所指的内存 即初始化父对象的vptr指针
; 7 : i = 1;
mov ecx, DWORD PTR _this$[ebp];将父对象的首地址给ecx
mov DWORD PTR [ecx+4], 1;将1存到偏移父对象首地址4byte处,即给父对象的成员变量i赋值
; 8 : }
mov eax, DWORD PTR _this$[ebp];父对象的首地址给eax 作为返回值。构造函数总是返回对象首地址
mov esp, ebp
pop ebp
ret 0
??0X@@QAE@XZ ENDP
从汇编码可以看出,在构造子类的过程中,依然调用了父类的构造函数,尽管父类是一个抽象类。但这只是为了初始化子对象中包含父对象的部分,如果直接想从父类实例化一个对象,编译器报错,这是因为父类的vtable不完全,编译器不能安全的创建一个抽象类对象。而在构造子对象的构成当中,虽然在构造子对象中所包含的的父对象部分,vptr暂时指向了父类的vtable,但是,当子对象构造完成时,vptr最终指向了子类的vtable。子类的vtable是一个完整的,因此编译器允许。
多态的晚捆绑机制只有在用地址或者引用调用虚函数的时候才有效,如果用对象本身直接调用虚函数,则不会出现晚捆绑,而是直接调用。










