心胸决定格局,眼界决定境界...

虚函数 3

一、通过父类型的指针访问子类自己的特有虚函数 

这样的程序根本无法编译通过。 

 

二、访问non-public的虚函数 
另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中, 
所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。 
如:

class Base { 
private:  
virtual void f() { cout << "Base::f" << endl; }  
};

class Derive : public Base{  
}; 
typedef void(*Fun)(void); 
void main() { 
Derive d; 
Fun pFun = (Fun)*((int*)*(int*)(&d)+0); 
pFun();  
}

对上面粗体部分的解释(@a && x):

1. (int*)(&d)取vptr地址,该地址存储的是指向vtbl的指针 
2. (int*)*(int*)(&d)取vtbl地址,该地址存储的是虚函数表数组 
3. (Fun)*((int*)*(int*)(&d) +0),取vtbl数组的第一个元素,即Base中第一个虚函数f的地址 
4. (Fun)*((int*)*(int*)(&d) +1),取vtbl数组的第二个元素(这第4点,如下图所示)。

下图也能很清晰的说明一些东西(@5):

 

如果一个子类重载的虚拟函数为privete,那么通过父类的指针可以访问到它吗?

#include    
class B   
{     
public:     
    virtual void fun()       
    {      
        std::cout << "base fun called";      
    };     
};  

class D : public B    
{     
private:   
    virtual void fun()       
    {      
        std::cout << "driver fun called";     
    };     
};  

int main(int argc, char* argv[])   
{        
    B* p = new D();     
    p->fun();     
    return 0;     
}  

运行时会输出 driver fun called

 

从这个实验,可以更深入的了解虚拟函数编译时的一些特征: 
在编译虚拟函数调用的时候,例如p->fun(); 只是按其静态类型来处理的, 在这里p的类型就是B,不会考虑其实际指向的类型(动态类型)。 
    也就是说,碰到p->fun();编译器就当作调用B的fun来进行相应的检查和处理。 
因为在B里fun是public的,所以这里在“访问控制检查”这一关就完全可以通过了。 
覆盖的化,然后就会转换成(*p->vptr[1])(p)这样的方式处理, p实际指向的动态类型是D, 

 

为了进一步的实验,可以将B里的fun改为private的,D里的改为public的,则编译就会出错。 

则运行会输出driver fun called, 1

关于这一点,Effective上讲的很清楚“virtual 函数系动态绑定, 而缺省参数却是静态绑定”, 
也就是说在编译的时候已经按照p的静态类型处理其默认参数了,转换成了(*p->vptr[1])(p, 1)这样的方式。

 

一个类如果有虚函数,不管是几个虚函数,都会为这个类声明一个虚函数表,这个虚表是一个含有虚函数的类的,不是说是类对象的。一个含有虚函数的类,不管有多少个数据成员,每个对象实例都有一个虚指针,在内存中,存放每个类对象的内存区,在内存区的头部都是先存放这个指针变量的(准确的说,应该是:视编译器具体情况而定),从第n(n视实际情况而定)个字节才是这个对象自己的东西。

 

posted @ 2015-09-20 22:20  WELEN  阅读(119)  评论(0)    收藏  举报