系被称为菱形继承。
二师兄:因为B和C各继承了一份A,当D继承B和C的时候就会有2份A;
#include <iostream>
struct A
{
int val = 42;
virtual void fun(){std::cout <<"A::fun" << std::endl;}
};
struct B: public A{ void fun() override{std::cout <<"B::fun" << std::endl;}};
struct C: public A{ void fun() override{std::cout <<"C::fun" << std::endl;}};
struct D: public B, public C{void fun() override{std::cout <<"D::fun" << std::endl;}};
int main(int argc, char const *argv[])
{
D d;
std::cout << d.val << std::endl; //编译失败,不知道调用从哪个类中继承的val变量
d.fun(); //编译失败,不知道调用从哪个类中继承的fun函数
}
二师兄:解决的办法有两种,一种是在调用符之前加上父类限定符:
std::cout << d.B::val << std::endl; //42
d.C::fun(); //C::fun
二师兄:但这里并没有解决数据冗余的问题,因为D中有B和C,而B和C各有一个虚表和一个int类型的成员变量,所以sizeof(D)
的大小是32(x86_64
架构,考虑到内存对齐)。
二师兄:所幸在C++11引入了虚继承(Virtual Inheritance
)机制,从源头上解决了这个问题:
#include <iostream>
struct A
{
int val = 42;
virtual void fun(){std::cout <<"A::fun" << std::endl;}
};
struct B: virtual public A{ void fun() override{std::cout <<"B::fun" << std::endl;}};
struct C: virtual public A{ void fun() override{std::cout <<"C::fun" << std::endl;}};
struct D: public B, public C{void fun() override{std::cout <<"D::fun" << std::endl;}};
int main(int argc, char const *argv[])
{
D d;
std::cout << d.val << std::endl; //42
d.fun(); //D::fun
}
二师兄:此时在对象d
中,只包含了一个val
和两个虚指针,成员变量的冗余问题得到解决。
面试官:一般我们认为多态会影响性能,你举得为什么影响性能?
二师兄:大多数人认为,虚函数的调用会先通过虚指针跳到虚函数表,然后通过偏移确定函数真实地址,再跳转到地址执行,是间接调用导致了性能损失。
二师兄:但实际上无法内联才是虚函数性能低于正常函数的主要原因。由于多态是运行时特征,在编译时编译器并不知道指针指向的函数地址,所以无法被内联。同时跳转到特定地址执行函数可能引发的L1 cache miss
(空间局部性不好),这也会影响性能。
面试官:虚函数的调用一定是非内联的吗?
二师兄:不是。现代编译器很聪明,如果编译器能够在编译时推断出真实的函数,可能会直接内联这个虚函数。虚函数的调用是否内联取决于编译器的实现和上下文。
面试官:你觉得多态在安全性上有没有什么问题?
二师兄:的确是有的。当我们把类中的虚函数定义为private
的时候,虽然我们不能通过类的对象去访问这个函数,但我们知道这个函数就在虚函数表中,可以通过特殊的方法(上文中已经给出示例)访问它:
#include <iostream>
struct Foo
{
private:
virtual void fun() {std::cout << "Foo::fun" << std::endl;}
};
int main(int argc, char const *argv[])
{
Foo f;
//f.fun(); //编译错误
using Fun = void(*)();
size_t* virtual_point = (size_t*)&f;
Fun* fun = (Fun*)*virtual_point;
(*fun)();
}
面试官:好的,今天的面试到这里就结束了,请回去等通知吧。
今天二师兄表现很不错,加个肉粽。感谢小伙伴的耐心阅读,祝各位小伙伴端午节牛逼(端午快乐->没文化,端午安康->跟风狗,好吧我祝各位端午牛逼)。二师兄的C++面试之旅,明天不见不散
关注我,带你21天“精通”C++!(狗头)