C++单类定义多接口

Edit

最近做ThoughtWorks的面试题,第一遍用最简单的方法实现了,面试官说没有OO设计,于是乎想炫一波OO设计,结果才发现,原来C++还差的很远。先来说说这个多接口的坑。还会有另外的笔记阐述:

  • 运算符重载与拷贝构造函数
  • 菱形继承
  • 函数中的按引用调用与返回
  • 以后想到再加

C++中的接口

接口是面向对象很重要的概念。按照《敏捷软件开发-原则、模式与实践》一书所述,所有软件高层不应依赖于低层,而应依赖于抽象出的接口。这就是依赖倒置原则(DIP)。Java中的接口用interface定义,C++中没有提供专门的关键字。而是用纯虚函数来表示接口。形如:

class interface
{
public:
virtual void func() = 0;
}

接口是不能实例化的,因为包含了纯虚函数。

单类定义多接口

在Java中很容易做到,只需class A implement IA, IB{}即可。在C++中可否这么做呢?很容易就想到C++中的多继承,如下:

class IA {
public:
virtual void funcA() = 0;
}
class IB {
public:
virtual void funcB() = 0;
}
class C : public IA, public IB {
void funcA();
void funcB();
}

按照最初的设想,我希望能够:

C* c = new C();
IA* pA = (IA*)c;
IB* pB = (IB*)pA;
pA->funcA(); // 调用A函数
pB->funcB(); // 调用B函数

但我发现,现实与理想差得很远。pB->funcB()调用的其实也是funcA()。原因与多继承内存分布与类型转换有关。
在发生多继承时,一个C对象中内存分布如下,盗用陈浩的博客C++ 对象的内存布局中的图:

Base1* pA = (Base1*)derive;
Base2* pB = (Base2*)derive;

当发生向上类型转换时,编译器均能很好的工作,不用依赖于各种强制转换,也可以识别到正确的函数。但是如果发生非正常的类型转换时,就会遇到问题。例如前文所述的将Base1型指针直接转型到Base2就会出问题。可参考这一篇博文:当C++多继承遇上类型转换。文中提到了三个解决方案,我尝试了前两个,分别用static_castdynamic_cast均不能解决。奇怪的是,dynamic_cast的解决方案看起来在网上确是颇有一些支持者,看下面的链接:

在我自己的code中,dynamic_cast之后的结果是虽然没有抛出bad cast异常,但是转换的结构是NULL。

解决方案

上面提到的方案,都不成功,但最终我还是找到了能解决的办法,尽管看起来不像想象中优雅。

方案一:采用菱形继承

根据查阅的各种网站资料,这样的继承会增加内存布局,以及调用的复杂度。但确实能解决上面多接口转型的问题。具体代码如下:

class IBase
class IA : virtual public IBase {}
class IB : virtual public IBase {}
class C : public IA, public IB {}
IA* pA = new C();
IBase* base = (IBase*)pA;
IB* pB = dynamic_cast<IB*>(base);

这样的做法,让人如鲠在喉,一个简单的调用,搞的这么复杂。转来转去,可读性极差,而且dynamic_cast还有性能问题。
而且这里还会遇到source type is not polymorphic的问题,解决方法使添加虚析构函数。

class IBase {
virtual ~IBase(){};
}
class IA : virtual public IBase {}
class IB : virtual public IBase {}
class C : public IA, public IB {}
IA* pA = new C();
IBase* base = (IBase*)pA;
IB* pB = dynamic_cast<IB*>(base);

方案二:采用单接口

这一方案,是受了下面的链接的启发:Implementing multiple interfaces in a single C++ class

As you found out, you cannot implement two interfaces with one single C++ class directly. However, you could simply define a new Slice interface derived from the two other Slice interfaces, and then implement this new Slice interface with a single C++ class.

作者说C++中直接定义多接口并不是一个好的实现,定义一个虚接口类来继承两个虚接口,实现类直接继承自这个虚类。代码如下:

class IA {}
class IB {}
class IAB : public IA, public IB {}
class C : public IAB {}

这样转型起来就方便多了

C* c = new C();
IA* pA = (IA*)c;
IB* pB = (IB*)pA; // 仍会出错,不能直接转
IB* pB2 = (IB*)(IAB*)pA; // 可以工作

但其实上层的代码,应该依赖于IAB,而不应该也不能够只依赖于IA, IB,而对IAB一无所知。

总结

  1. 在设计接口的时候,不要指望用多继承来模仿Java里的多接口定义。
  2. 尽量避免在多继承的时候,需要在没有继承关系(IA & IB)之间进行指针转型。虽然菱形继承+dynamic_cast可以做到,但是可读性和性能会下降。
  3. 可以用方案二中的方法来实现近似的多接口定义。这是一个相对简介有效的方法。
%23%20C++%u5355%u7C7B%u5B9A%u4E49%u591A%u63A5%u53E3%0A@%28myblog%29%5Bc/c++%5D%0A%0A%u6700%u8FD1%u505AThoughtWorks%u7684%u9762%u8BD5%u9898%uFF0C%u7B2C%u4E00%u904D%u7528%u6700%u7B80%u5355%u7684%u65B9%u6CD5%u5B9E%u73B0%u4E86%uFF0C%u9762%u8BD5%u5B98%u8BF4%u6CA1%u6709OO%u8BBE%u8BA1%uFF0C%u4E8E%u662F%u4E4E%u60F3%u70AB%u4E00%u6CE2OO%u8BBE%u8BA1%uFF0C%u7ED3%u679C%u624D%u53D1%u73B0%uFF0C%u539F%u6765C++%u8FD8%u5DEE%u7684%u5F88%u8FDC%u3002%u5148%u6765%u8BF4%u8BF4%u8FD9%u4E2A%u591A%u63A5%u53E3%u7684%u5751%u3002%u8FD8%u4F1A%u6709%u53E6%u5916%u7684%u7B14%u8BB0%u9610%u8FF0%uFF1A%0A-%20%u8FD0%u7B97%u7B26%u91CD%u8F7D%u4E0E%u62F7%u8D1D%u6784%u9020%u51FD%u6570%0A-%20%u83F1%u5F62%u7EE7%u627F%0A-%20%u51FD%u6570%u4E2D%u7684%u6309%u5F15%u7528%u8C03%u7528%u4E0E%u8FD4%u56DE%0A-%20%u4EE5%u540E%u60F3%u5230%u518D%u52A0%0A%0A%23%23%20C++%u4E2D%u7684%u63A5%u53E3%0A%u63A5%u53E3%u662F%u9762%u5411%u5BF9%u8C61%u5F88%u91CD%u8981%u7684%u6982%u5FF5%u3002%u6309%u7167%u300A%u654F%u6377%u8F6F%u4EF6%u5F00%u53D1-%u539F%u5219%u3001%u6A21%u5F0F%u4E0E%u5B9E%u8DF5%u300B%u4E00%u4E66%u6240%u8FF0%uFF0C%u6240%u6709%u8F6F%u4EF6%u9AD8%u5C42%u4E0D%u5E94%u4F9D%u8D56%u4E8E%u4F4E%u5C42%uFF0C%u800C%u5E94%u4F9D%u8D56%u4E8E%u62BD%u8C61%u51FA%u7684%u63A5%u53E3%u3002%u8FD9%u5C31%u662F%u4F9D%u8D56%u5012%u7F6E%u539F%u5219%28DIP%29%u3002Java%u4E2D%u7684%u63A5%u53E3%u7528%60interface%60%u5B9A%u4E49%uFF0CC++%u4E2D%u6CA1%u6709%u63D0%u4F9B%u4E13%u95E8%u7684%u5173%u952E%u5B57%u3002%u800C%u662F%u7528%u7EAF%u865A%u51FD%u6570%u6765%u8868%u793A%u63A5%u53E3%u3002%u5F62%u5982%uFF1A%0A%60%60%60%0Aclass%20interface%0A%7B%0Apublic%3A%0A%09virtual%20void%20func%28%29%20%3D%200%3B%0A%7D%0A%60%60%60%0A%u63A5%u53E3%u662F%u4E0D%u80FD%u5B9E%u4F8B%u5316%u7684%uFF0C%u56E0%u4E3A%u5305%u542B%u4E86%u7EAF%u865A%u51FD%u6570%u3002%0A%0A%23%23%u5355%u7C7B%u5B9A%u4E49%u591A%u63A5%u53E3%0A%u5728Java%u4E2D%u5F88%u5BB9%u6613%u505A%u5230%uFF0C%u53EA%u9700%60class%20A%20implement%20IA%2C%20IB%7B%7D%60%u5373%u53EF%u3002%u5728C++%u4E2D%u53EF%u5426%u8FD9%u4E48%u505A%u5462%uFF1F%u5F88%u5BB9%u6613%u5C31%u60F3%u5230C++%u4E2D%u7684%u591A%u7EE7%u627F%uFF0C%u5982%u4E0B%uFF1A%0A%60%60%60%0Aclass%20IA%20%7B%0Apublic%3A%0A%09virtual%20void%20funcA%28%29%20%3D%200%3B%0A%7D%0Aclass%20IB%20%7B%0Apublic%3A%0A%09virtual%20void%20funcB%28%29%20%3D%200%3B%0A%7D%0Aclass%20C%20%3A%20public%20IA%2C%20public%20IB%20%7B%0A%09void%20funcA%28%29%3B%0A%09void%20funcB%28%29%3B%0A%7D%0A%60%60%60%0A%u6309%u7167%u6700%u521D%u7684%u8BBE%u60F3%uFF0C%u6211%u5E0C%u671B%u80FD%u591F%uFF1A%0A%60%60%60%0AC*%20c%20%3D%20new%20C%28%29%3B%0AIA*%20pA%20%3D%20%28IA*%29c%3B%0AIB*%20pB%20%3D%20%28IB*%29pA%3B%0ApA-%3EfuncA%28%29%3B%20//%20%u8C03%u7528A%u51FD%u6570%0ApB-%3EfuncB%28%29%3B%20//%20%u8C03%u7528B%u51FD%u6570%0A%60%60%60%0A%u4F46%u6211%u53D1%u73B0%uFF0C%u73B0%u5B9E%u4E0E%u7406%u60F3%u5DEE%u5F97%u5F88%u8FDC%u3002%60pB-%3EfuncB%28%29%60%u8C03%u7528%u7684%u5176%u5B9E%u4E5F%u662F%60funcA%28%29%60%u3002%u539F%u56E0%u4E0E%u591A%u7EE7%u627F%u5185%u5B58%u5206%u5E03%u4E0E%u7C7B%u578B%u8F6C%u6362%u6709%u5173%u3002%0A%u5728%u53D1%u751F%u591A%u7EE7%u627F%u65F6%uFF0C%u4E00%u4E2AC%u5BF9%u8C61%u4E2D%u5185%u5B58%u5206%u5E03%u5982%u4E0B%uFF0C%u76D7%u7528%u9648%u6D69%u7684%u535A%u5BA2%5BC++%20%u5BF9%u8C61%u7684%u5185%u5B58%u5E03%u5C40%5D%28http%3A//blog.csdn.net/haoel/article/details/3081328%29%u4E2D%u7684%u56FE%uFF1A%0A%21%5BAlt%20text%5D%28./1520307655931.png%29%20%21%5BAlt%20text%5D%28./1520307665220.png%29%0A%0A%60%60%60%0ABase1*%20pA%20%3D%20%28Base1*%29derive%3B%0ABase2*%20pB%20%3D%20%28Base2*%29derive%3B%0A%60%60%60%0A%u5F53%u53D1%u751F%u5411%u4E0A%u7C7B%u578B%u8F6C%u6362%u65F6%uFF0C%u7F16%u8BD1%u5668%u5747%u80FD%u5F88%u597D%u7684%u5DE5%u4F5C%uFF0C%u4E0D%u7528%u4F9D%u8D56%u4E8E%u5404%u79CD%u5F3A%u5236%u8F6C%u6362%uFF0C%u4E5F%u53EF%u4EE5%u8BC6%u522B%u5230%u6B63%u786E%u7684%u51FD%u6570%u3002%u4F46%u662F%u5982%u679C%u53D1%u751F%u975E%u6B63%u5E38%u7684%u7C7B%u578B%u8F6C%u6362%u65F6%uFF0C%u5C31%u4F1A%u9047%u5230%u95EE%u9898%u3002%u4F8B%u5982%u524D%u6587%u6240%u8FF0%u7684%u5C06%60Base1%60%u578B%u6307%u9488%u76F4%u63A5%u8F6C%u578B%u5230%60Base2%60%u5C31%u4F1A%u51FA%u95EE%u9898%u3002%u53EF%u53C2%u8003%u8FD9%u4E00%u7BC7%u535A%u6587%uFF1A%5B%u5F53C++%u591A%u7EE7%u627F%u9047%u4E0A%u7C7B%u578B%u8F6C%u6362%5D%28http%3A//blog.csdn.net/smstong/article/details/24455371%29%u3002%u6587%u4E2D%u63D0%u5230%u4E86%u4E09%u4E2A%u89E3%u51B3%u65B9%u6848%uFF0C%u6211%u5C1D%u8BD5%u4E86%u524D%u4E24%u4E2A%uFF0C%u5206%u522B%u7528%60static_cast%60%u548C%60dynamic_cast%60%u5747%u4E0D%u80FD%u89E3%u51B3%u3002%u5947%u602A%u7684%u662F%uFF0C%60dynamic_cast%60%u7684%u89E3%u51B3%u65B9%u6848%u770B%u8D77%u6765%u5728%u7F51%u4E0A%u786E%u662F%u9887%u6709%u4E00%u4E9B%u652F%u6301%u8005%uFF0C%u770B%u4E0B%u9762%u7684%u94FE%u63A5%uFF1A%0A-%20%5BMultiple%20inheritance%20casting%20from%20base%20class%20to%20a%20different%20derived%20class%0A%5D%28https%3A//stackoverflow.com/questions/15242841/multiple-inheritance-casting-from-base-class-to-a-different-derived-class%29%0A-%20%5BType%20casting%20to%20an%20interface%20%28Abstract%20class%29%20in%20case%20of%20Multiple%20Inheritance%20in%20C++%0A%5D%28https%3A//stackoverflow.com/questions/14121297/type-casting-to-an-interface-abstract-class-in-case-of-multiple-inheritance-in%29%0A-%20%5BHow%20to%20perform%20casting%20with%20multiple%20inheritance%0A%5D%28https%3A//stackoverflow.com/questions/5715865/how-to-perform-casting-with-multiple-inheritance%29%0A%0A%u5728%u6211%u81EA%u5DF1%u7684code%u4E2D%uFF0C%60dynamic_cast%60%u4E4B%u540E%u7684%u7ED3%u679C%u662F%u867D%u7136%u6CA1%u6709%u629B%u51FAbad%20cast%u5F02%u5E38%uFF0C%u4F46%u662F%u8F6C%u6362%u7684%u7ED3%u6784%u662FNULL%u3002%0A%0A%23%23%20%u89E3%u51B3%u65B9%u6848%0A%u4E0A%u9762%u63D0%u5230%u7684%u65B9%u6848%uFF0C%u90FD%u4E0D%u6210%u529F%uFF0C%u4F46%u6700%u7EC8%u6211%u8FD8%u662F%u627E%u5230%u4E86%u80FD%u89E3%u51B3%u7684%u529E%u6CD5%uFF0C%u5C3D%u7BA1%u770B%u8D77%u6765%u4E0D%u50CF%u60F3%u8C61%u4E2D%u4F18%u96C5%u3002%0A%23%23%23%20%u65B9%u6848%u4E00%uFF1A%u91C7%u7528%u83F1%u5F62%u7EE7%u627F%0A%u6839%u636E%u67E5%u9605%u7684%u5404%u79CD%u7F51%u7AD9%u8D44%u6599%uFF0C%u8FD9%u6837%u7684%u7EE7%u627F%u4F1A%u589E%u52A0%u5185%u5B58%u5E03%u5C40%uFF0C%u4EE5%u53CA%u8C03%u7528%u7684%u590D%u6742%u5EA6%u3002%u4F46%u786E%u5B9E%u80FD%u89E3%u51B3%u4E0A%u9762%u591A%u63A5%u53E3%u8F6C%u578B%u7684%u95EE%u9898%u3002%u5177%u4F53%u4EE3%u7801%u5982%u4E0B%uFF1A%0A%60%60%60%0Aclass%20IBase%0Aclass%20IA%20%3A%20virtual%20public%20IBase%20%7B%7D%0Aclass%20IB%20%3A%20virtual%20public%20IBase%20%7B%7D%0Aclass%20C%20%3A%20public%20IA%2C%20public%20IB%20%7B%7D%0A%0AIA*%20pA%20%3D%20new%20C%28%29%3B%0AIBase*%20base%20%3D%20%28IBase*%29pA%3B%0AIB*%20pB%20%3D%20dynamic_cast%3CIB*%3E%28base%29%3B%0A%60%60%60%20%0A%u8FD9%u6837%u7684%u505A%u6CD5%uFF0C%u8BA9%u4EBA%u5982%u9CA0%u5728%u5589%uFF0C%u4E00%u4E2A%u7B80%u5355%u7684%u8C03%u7528%uFF0C%u641E%u7684%u8FD9%u4E48%u590D%u6742%u3002%u8F6C%u6765%u8F6C%u53BB%uFF0C%u53EF%u8BFB%u6027%u6781%u5DEE%uFF0C%u800C%u4E14%60dynamic_cast%60%u8FD8%u6709%u6027%u80FD%u95EE%u9898%u3002%0A%u800C%u4E14%u8FD9%u91CC%u8FD8%u4F1A%u9047%u5230source%20type%20is%20not%20polymorphic%u7684%u95EE%u9898%uFF0C%u89E3%u51B3%u65B9%u6CD5%u4F7F%u6DFB%u52A0%u865A%u6790%u6784%u51FD%u6570%u3002%0A%60%60%60%0Aclass%20IBase%20%7B%0A%09virtual%20%7EIBase%28%29%7B%7D%3B%0A%7D%0Aclass%20IA%20%3A%20virtual%20public%20IBase%20%7B%7D%20%0Aclass%20IB%20%3A%20virtual%20public%20IBase%20%7B%7D%0Aclass%20C%20%3A%20public%20IA%2C%20public%20IB%20%7B%7D%0A%0AIA*%20pA%20%3D%20new%20C%28%29%3B%0AIBase*%20base%20%3D%20%28IBase*%29pA%3B%0AIB*%20pB%20%3D%20dynamic_cast%3CIB*%3E%28base%29%3B%0A%60%60%60%20%0A%0A%23%23%23%20%u65B9%u6848%u4E8C%uFF1A%u91C7%u7528%u5355%u63A5%u53E3%0A%u8FD9%u4E00%u65B9%u6848%uFF0C%u662F%u53D7%u4E86%u4E0B%u9762%u7684%u94FE%u63A5%u7684%u542F%u53D1%uFF1A%5BImplementing%20multiple%20interfaces%20in%20a%20single%20C++%20class%5D%28https%3A//forums.zeroc.com/discussion/4994/implementing-multiple-interfaces-in-a-single-c-class%29%0A%0A%3E%20As%20you%20found%20out%2C%20**you%20cannot%20implement%20two%20interfaces%20with%20one%20single%20C++%20class%20directly**.%20However%2C%20you%20could%20simply%20define%20a%20new%20Slice%20interface%20derived%20from%20the%20two%20other%20Slice%20interfaces%2C%20and%20then%20implement%20this%20new%20Slice%20interface%20with%20a%20single%20C++%20class.%0A%0A%u4F5C%u8005%u8BF4C++%u4E2D%u76F4%u63A5%u5B9A%u4E49%u591A%u63A5%u53E3%u5E76%u4E0D%u662F%u4E00%u4E2A%u597D%u7684%u5B9E%u73B0%uFF0C%u5B9A%u4E49%u4E00%u4E2A%u865A%u63A5%u53E3%u7C7B%u6765%u7EE7%u627F%u4E24%u4E2A%u865A%u63A5%u53E3%uFF0C%u5B9E%u73B0%u7C7B%u76F4%u63A5%u7EE7%u627F%u81EA%u8FD9%u4E2A%u865A%u7C7B%u3002%u4EE3%u7801%u5982%u4E0B%3A%0A%60%60%60%0Aclass%20IA%20%7B%7D%0Aclass%20IB%20%7B%7D%0Aclass%20IAB%20%3A%20public%20IA%2C%20public%20IB%20%7B%7D%0Aclass%20C%20%3A%20public%20IAB%20%7B%7D%0A%60%60%60%0A%u8FD9%u6837%u8F6C%u578B%u8D77%u6765%u5C31%u65B9%u4FBF%u591A%u4E86%0A%60%60%60%0AC*%20c%20%3D%20new%20C%28%29%3B%0AIA*%20pA%20%3D%20%28IA*%29c%3B%0AIB*%20pB%20%3D%20%28IB*%29pA%3B%20//%20%u4ECD%u4F1A%u51FA%u9519%uFF0C%u4E0D%u80FD%u76F4%u63A5%u8F6C%0AIB*%20pB2%20%3D%20%28IB*%29%28IAB*%29pA%3B%20//%20%u53EF%u4EE5%u5DE5%u4F5C%0A%60%60%60%0A%u4F46%u5176%u5B9E%u4E0A%u5C42%u7684%u4EE3%u7801%uFF0C%u5E94%u8BE5%u4F9D%u8D56%u4E8E%60IAB%60%uFF0C%u800C%u4E0D%u5E94%u8BE5%u4E5F%u4E0D%u80FD%u591F%u53EA%u4F9D%u8D56%u4E8E%60IA%60%2C%20%60IB%60%uFF0C%u800C%u5BF9%60IAB%60%u4E00%u65E0%u6240%u77E5%u3002%0A%0A%23%23%20%u603B%u7ED3%0A1.%20%u5728%u8BBE%u8BA1%u63A5%u53E3%u7684%u65F6%u5019%uFF0C%u4E0D%u8981%u6307%u671B%u7528%u591A%u7EE7%u627F%u6765%u6A21%u4EFFJava%u91CC%u7684%u591A%u63A5%u53E3%u5B9A%u4E49%u3002%0A2.%20%u5C3D%u91CF%u907F%u514D%u5728%u591A%u7EE7%u627F%u7684%u65F6%u5019%uFF0C%u9700%u8981%u5728%u6CA1%u6709%u7EE7%u627F%u5173%u7CFB%28%60IA%60%20%26%20%60IB%60%29%u4E4B%u95F4%u8FDB%u884C%u6307%u9488%u8F6C%u578B%u3002%u867D%u7136%u83F1%u5F62%u7EE7%u627F+%60dynamic_cast%60%u53EF%u4EE5%u505A%u5230%uFF0C%u4F46%u662F%u53EF%u8BFB%u6027%u548C%u6027%u80FD%u4F1A%u4E0B%u964D%u3002%0A3.%20%u53EF%u4EE5%u7528%u65B9%u6848%u4E8C%u4E2D%u7684%u65B9%u6CD5%u6765%u5B9E%u73B0%u8FD1%u4F3C%u7684%u591A%u63A5%u53E3%u5B9A%u4E49%u3002%u8FD9%u662F%u4E00%u4E2A%u76F8%u5BF9%u7B80%u4ECB%u6709%u6548%u7684%u65B9%u6CD5%u3002%0A