虚函数的继承

C++2.0以后全面支持虚函数与虚继承,这两个特性的引入为C++增强了不少功能,也引入了不少烦恼。虚函数与虚继承有哪些特性,今天就不记录了,如果能搞了解一下编译器是如何实现虚函数和虚继承,它们在类的内存空间中又是如何布局的,却可以对C++的了解深入不少。这段时间花了一些时间了解这些玩意,搞得偶都,不过总算有些收获,嘿嘿。
  
  先看一段代码
  class A
  {
   virtual aa(){};
  };
  
  class B : public virtual A
  {
   char j[3]; //加入一个变量是为了看清楚class中的vfptr放在什么位置
   public:
   virtual bb(){};
  };
  class C : public virtual B
  {
   char i[3];
   public:
   virtual cc(){};
  };
  
  这次先不给结果,先分析一下,也好加强一下印象。
  1、对于class A,由于只有一个虚函数,那么必须得有一个对应的虚函数表,来记录对应的函数入口地址。同时在class A的内存空间中之需要有个vfptr_A指向该表。sizeof(A)也很容易确定,为4。
  2、对于class B,由于class B虚基础了class A,同时还拥有自己的虚函数。那么class B中首先拥有一个vfptr_B,指向自己的虚函数表。还有char j[3],做一次alignment,一般大小为4。可虚继承该如何实现咧?this is 啊 problem!偶之前是不晓得的,还好C++ Object Model上有介绍。首先要通过加入一个虚l类指针(记vbptr_B_A)来指向其父类,然后还要包含父类的所有内容。有些复杂了,不过还不难想象。sizeof(B)= 4+4+4+4=16(vfptr_B、char j[3]做alignment、vbptr_B_A和class A)。
  3、在接着是class C了。class C首先也得有个vfptr_C,然后是char i[3],然后是vbptr_C_B,然后是class B,所以sizeof(C)=4+4+4+16=28(vfptr_C、char i[3]做alignment、vbptr_C_A和class B)。
  
  在VC 6.0下写了个程序,把上面几个类的大小打印出来,果然结果为4、16、28。hoho搞定!真的搞定了?也许经过上面的分析,虽然每个类具体的内存布局还不大清楚,但其中的内容应该不会错了。嘿嘿,在没跟踪时偶确实也是这么想的,但结果却是……
  
  VC中虚继承的内存布局——单继承
  画了个图,简单表示一下我跟踪后的结果
  

  class A的情况太简单,没问题。从class B的内存布局图可以得出下面的结论。
  1、vf_ptr B放在了类的首部,那么如果要想直接拿memcpy完成类的复制是很危险的,用struct也是不行的。改天再深入学习一下struct 和class的区别,可以看出这里的差别来。
  2、vbtbl_ptr_B,为什么不是先前我描述的vbptr_B_A呢?因为这个指针与我先前猜测的内容有很大区别。这个指针指向的是class B的虚类表(嗯,俺自个儿起的名字,实在是学艺不精)。看看VB table,VB table有两项,第一项为FFFFFFFC,这一项的值可能没啥意义,可能是为了保证虚类表不为空吧。第二项为8,看起来像是class B中的class A相对该vbtbl_ptr_B的位移,也就是一个offset。类似的方法在C++ Object Model(P121)有介绍,可以去看看。
  class C的内存布局就比较复杂了,不过它的内存布局也更一步说明我对vbtbl_ptr_B中的内容,也就是虚类表的理解是正确的。不过值得关注的是class B中的class A在布局时被移到前面去了,虽然整个大小没变,但这样一来如果做这样的操作 C c; B *b;b=&c;时b的操作如何呢?此时只要从c的虚类表里获得class B的位置既可赋值给b。但是在构建class C时会复杂一些,后面的使用还是非常简单的,效率也比较高。class A的内存布局被前移可能是考虑倒C的虚继承顺序吧。
  
### 虚函数继承的关系 在 C++ 中,虚函数用于支持多态行为。当基类中的某个成员函数被声明为 `virtual` 后,在派生类中重写该函数时会自动成为虚函数[^4]。 #### 单一继承下的虚函数机制 单一继承下定义虚函数的方式如下: ```cpp class Base { public: virtual void show() const { std::cout << "Base\n"; } }; class Derived : public Base { public: void show() const override { std::cout << "Derived\n"; } // 自动视为虚函数 }; ``` 这里即使未显式标注 `override` 或者 `virtual` 关键字,由于是从基类继承而来并进行了覆盖操作,因此仍会被当作虚函数处理。 #### 多重继承场景 对于多重继承而言,如果多个父类中有相同签名的方法,则需特别注意避免二义性问题。此时可以利用完全限定名来指定具体调用哪个版本的方法;而为了确保正确的行为模式——即总是调用最深层级的派生类所提供的实现版本——应当保证这些同名方法均为虚函数。 ```cpp #include <iostream> using namespace std; // 基础类A拥有一个名为getName的虚函数 class A { public: virtual string getName() const { return "Class A"; } }; // 类B和C分别从A单继承,并各自实现了getName() class B : public A { public: string getName() const override { return "Class B"; } }; class C : public A { public: string getName() const override { return "Class C"; } }; int main(){ A* p; C obj_c; p=&obj_c; cout<<p->getName()<<endl; // 输出:"Class C" } ``` 上述例子展示了即便不是直接子类也能通过指针访问到最终派生类的对象所对应的虚函数版本。 #### 使用纯虚函数构建抽象接口 有时希望创建仅提供接口而不给出任何实际功能的具体形式,这时就可以采用含有至少一个纯虚函数(`=0`) 的类作为基础模板: ```cpp class Shape { public: virtual double area() const = 0; // 定义了一个纯虚函数 virtual ~Shape() {} // 析构也应设成虚函数以防止资源泄漏 }; class Circle : public Shape { private: double radius_; public: explicit Circle(double r) :radius_(r){} double area() const override{ return M_PI * pow(radius_,2); } }; ``` 以上代码片段说明了如何借助于纯虚函数建立起一种强制性的契约关系,使得所有非叶子节点都必须去完成特定职责的规定动作[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值