介绍C++中面向对象相关知识。
继承和多态
常见问题
数据成员有多态性么?
如下所示,答案是没有,取决于用什么指针去访问。其实也很容易想到,只有函数才会创建虚表。
1 |
|
public、protected、private 继承的区别是什么?
特别注意,struct 默认继承是 public;class 默认是 private。
一般涉及到虚函数的继承,都得是 public 的,否则在将派生类指针赋值给基类指针时,会报错”error: ‘B’ is an inaccessible base of ‘D’”。
如何声明一个抽象类(纯虚类)?
只要有一个纯虚函数的类就是抽象类。但注意,抽象类中必须定义一个虚的析构函数,并且不能是纯虚的,否则会链接错误。
如何处理同名数据成员和成员函数(非 virual 情况)?
如下所示,默认情况下是访问的派生类。但可以用->B::x
或.B::x
来限定访问基类,或者直接使用基类指针。
1 |
|
一旦继承,这些同名成员其实都是会保存两份的。比如在 https://github.com/pingcap/tiflash/pull/6041 中我就同时用了继承+持有的方式去实现:通过继承,可以复用接口;通过持有,可以复用实现。但这样的问题就是 SSTReader
的私有成员其实被重复创建了。一种方式是改成 protected,让 MultiSSTReader 共享。但其实这样会破坏我们“只是复用接口才继承”的目的。这么做比较炫技,更 neat 一点的方式是让 SSTReader 变成抽象类。
1 | class SSTReader { |
逆变与协变
我们知道在 C++ 中,如果有class D:B
,则std::vector<D>
和std::vector<B>
是没有任何继承关系的,也就是 invariance 的。
但是在另外一些语言中,存在协变(covariant)和逆变(contravariance)两种关系。协变简而言之就是如果struct D:B
,则C<D>:C<B>
,而逆变则翻转了继承关系,有C<B>:C<D>
。
一般语言中,协变是比较常见的,看起来更合乎逻辑。例如把一个[] Cat
数组看做一个[] Animal
数组也没什么不对。
C++ 中的 virtual 函数机制其实又被称为协变返回类型。
多重继承和虚继承
模板编程和多态
能否声明一个模板成员函数为虚的呢?
1 | struct Cls{ |
答案是不行的。为此,需要介绍两个概念
模板实例化(instantiation),C++中的模板既不是一个类型,也不是一个对象,也不是一个实体。在C++编译器生成后的real code中,不存在任何模板的概念。那么这个实例化的顺序是怎么样的呢,是整个程序先扫一遍,完成一次性的“替换调用”,还是链式的呢?这里涉及到point of instantiation的概念,详情可参考我的文章stateful constexpr,简单地来说,就是我要用到了就去实例化。
因此,某个函数模板到底会被实例化多少次,那是要等编译结束才会知道的。那么同样需要在编译结束才能知道的是一个类的布局。因为C++需要看到整个类的定义(而不是声明)才能为这个类生成代码,而虚函数所依赖的虚表需要在类被编译完成时才能确定。
其实大家写 Rust 对这一点就会感触很深。