"一次实现"可以在某些场合替代菱形继承?

前不久在C++板块请教过这样一个问题:“多个基类中相同的纯虚函数,只需在派生类中实现一次”是否是标准行为。这个问题直到现在我也还没能确定,不过我在VC2005、VC2008和g++上都试过,该特性在所有这些编译器上都可以正常工作,所以应该八九不离十了。另外,记得当时星星有“语法正确逻辑说不过去”一说,那时想想确实也有些别扭。不过,最近在工作中又遇到了一个类似的问题,再加上一些突发奇想,于是产生了标题中的这个想法。考虑下面的三个类:

 
 

   
ClientOne通过调用Server的method_one()和method_two()方法和Server交互,ClientTwo通过调用Server的余下方法与Server交互。通常在设计良好的OO项目中,为了降低各对象之间的依赖,像这种问题都需要提取接口,并根据客户对接口进行隔离,那么,上面的依赖关系就会变成下面这样:

 
 

   
现在Server只要直接实现InterfaceOne和InterfaceTwo这两个接口就行了,接口实现在C++中表现为继承,所以在C++中Server直接派生自这两个基类就行了。问题似乎解决了。但如果ClientOne和ClientTwo都需要通过另一个叫做“shared_method()"的方法与Server交互,那会怎样?对,继续提取接口:

 
 

   
这里出现了菱形继承,所以在C++中就需要使用虚继承,代码就会是这样:

    class
SharedInterface {
   
public:
        virtual void
shared_method() = 0;
        virtual
~SharedInterface() {}
   
};
        
    class
InterfaceOne : public virtual SharedInterface {
   
public:
        virtual void method_one()
= 0;
        virtual void method_two() =
0;
        virtual ~InterfaceOne()
{}
    };
    
    class
InterfaceTwo : public virtual SharedInterface {
   
public:
        virtual void
method_three() = 0;
        virtual void
method_four() = 0;
        virtual
~InterfaceTwo() {}
   
};
        
    class
Server : public InterfaceOne, public InterfaceTwo {
   
public:
        virtual void
method_one();
        virtual void
method_two();
        virtual void
method_three();
        virtual void
method_four();
        virtual void
shared_method();
       
...
    };     
 

    有些时候并不是所有人都愿意使用虚继承,所以某些人就会突发奇想,看看自吸磁力泵能否回避之。我就是其中之一。根据”一次实现“,我把shared_method下推至InterfaceOne和InterfaceTwo,这样就可以把SharedInterface去掉:

 
 

   
哈哈,不但去掉了虚继承,还直接少了一层继承,这个世界干净啦!当然,这个方法对于两个接口中存在很少部分的重叠方法还是挺方便的,但如果重叠方法过多,还是应该提取出一个公共接口,以避免自吸磁力泵过多的重复。

评论列表
周星星2014-4-7
15:44:47
re:
“一次实现”可以在某些场合替代棱形继承?

从编译器实现手段上讲,你的这个做法应该是安全的。

"有些时候并不是所有人都愿意使用虚继承"
---
虚继承挺好的呀!我觉得虚继承唯一的缺点是
如果不知道当时或以后会有InterfacdeTwo的话则无法确定InterfacdeOne需不需要虚继承SharedInterface。

如果接口交叉过多的话,使用虚继承可能层次变得非常复杂且难于扩充,可以使用另一种解决手段是,
class
Server
{
public:
    void
shared_method();
    void method_one();
   
void method_two();
    void
method_three();
    void method_four();
};

//
InterfaceOne.hpp
class Server;
class
InterfaceOne
{
public:
    void
shared_method();
    void method_one();
   
void method_two();
private:http://www.hrbust.edu.cn/
   
Server* pServer_;
};
// InterfaceOne.hpp
#include "Server.hpp"
void
InterfaceOne::shared_method() { pServer_->shared_method(); }
void
InterfaceOne::method_one() { pServer_->method_one(); }
void
InterfaceOne::method_two() { pServer_->method_two(); }
笨笨猪2014-4-7
15:44:47
re:
“一次实现”可以在某些场合替代棱形继承?
TO:星星
你的这种思想很好!很多时候我们的确是在滥用继承关系,经常在需要使用委托的时候却选择了继承。不过对于当前这个问题,我觉得继承要比委托更恰当,我想到的有两方面原因:
1.
因为我在这里要表达的是接口与实现之间的关系(我所说的接口指的是纯抽象类,不包含任何状态,我们只能通过生成子类的实例来使用它),而不是其他的什么关系。
2.
继承比委托更简单,虽然委托具有更大的灵活性,但在这里不必要。如果在这个问题上使用委托,我们就必须再额外多管理两个对象的生命周期,因为现在InterfaceOne和InterfaceTwo变成了具体类,而不是接口,必须要被创建和管理。

我并不是说虚继承怎么样不好,而是引入虚继承之后,就出现了不一致性,我必须要时刻记住哪里该用常规继承,哪里该用虚继承。
另外,我觉得这个问题的更本原因是因为C++不支持interface关键字造成的。对于那些有状态的基类,菱形继承中如果不使用虚继承肯定会产生二义性,但是对interface这样的无状态的纯抽象基类,菱形继承中不存在任何二义性

时间: 2024-11-05 02:24:50

"一次实现"可以在某些场合替代菱形继承?的相关文章

python核心编程--笔记

python核心编程--笔记 的解释器options: 1.1 –d   提供调试输出 1.2 –O   生成优化的字节码(生成.pyo文件) 1.3 –S   不导入site模块以在启动时查找python路径 1.4 –v   冗余输出(导入语句详细追踪) 1.5 –m mod 将一个模块以脚本形式运行 1.6 –Q opt 除法选项(参阅文档) 1.7 –c cmd 运行以命令行字符串心事提交的python脚本 1.8 file   以给定的文件运行python脚本 2 _在解释器中表示最后

[转]sprintf

转自:http://blog.csdn.net/masibuaa/article/details/5634886 在将各种类型的数据构造成字符串时,sprintf 的强大功能很少会让你失望.由于sprintf 跟printf 在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出.这也导致sprintf 比printf 有用得多. sprintf 是个变参函数,定义如下: int sprintf( char *buffer, const char *format

Google's C++ coding style

v0.2 - Last updated November 8, 2013 源自 Google's C++ coding style rev. 3.274 目录 由 DocToc生成     头文件        #define用法        前向声明        内联函数        -inl.h文件        函数参数顺序        include的命名和顺序    作用域        命名空间            未命名空间            命名空间       

计算机程序的思维逻辑 (56) - 文件概述

我们在日常电脑操作中,接触和处理最多的,除了上网,大概就是各种各样的文件了,从本节开始,我们就来探讨文件处理,本节主要介绍文件有关的一些基本概念和常识,Java中处理文件的基本思路和类结构,以及接来下章节的安排思路. 基本概念和常识 二进制思维 为了透彻理解文件,我们首先要有一个二进制思维.所有文件,不论是可执行文件.图片文件.视频文件.Word文件.压缩文件.txt文件,都没什么可神秘的,它们都是以0和1的二进制形式保存的.我们所看到的图片.视频.文本,都是应用程序对这些二进制的解析结果. 作

Effective C++ -- 继承和面向对象设计

32.确保你的public继承了模is-a关系 public继承意味着is-a关系(里氏替换原则),一切适用于基类也适用于派生类. 矩形继承正方形问题: 可实施与矩形的操作无法实施与正方形 在编程领域.正方形是一种矩形是错误的 在现实领域,正方形是一种矩形是正确的 33.避免遮盖继承而来的名称 class Base { private: int x; public: virtual void mf1() = 0; virtual void mf1(int); virtual void mf2()

Google开发规范

v0.2 - Last updated November 8, 2013 源自 Google's C++ coding style rev. 3.274 目录 由 DocToc生成     头文件        #define用法        前向声明        内联函数        -inl.h文件        函数参数顺序        include的命名和顺序    作用域        命名空间            未命名空间            命名空间       

各种List、Map、Set的比较

List: 1.ArrayList:使用数组存储,ArrayList会比Vector快,他是非同步的,如果设计涉及到多线程,还是用Vector比较好一些: 2.Vector:其方法都是同步的,除非需要同步的场合,否则基本被其同门师弟ArrayList替代: 3.Stack:实现了先入后出: 4.LinkedList:双向列表存储: Map: 1.HashMap:是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key)

C++ stringstream介绍,使用方法与例子

From: http://www.usidcbbs.com/read-htm-tid-1898.html C++引入了ostringstream.istringstream.stringstream这三个类,要使用他们创建对象就必须包含sstream.h头文件. istringstream类用于执行C++风格的串流的输入操作. ostringstream类用于执行C风格的串流的输出操作. strstream类同时可以支持C风格的串流的输入输出操作. istringstream类是从istream

C++11学习

C++11学习 本章目的: 当Android用ART虚拟机替代Dalvik的时候,为了表示和Dalvik彻底划清界限的决心,Google连ART虚拟机的实现代码都切换到了C++11.C+11的标准规范于2011年2月正式落稿,而此前10余年间,C++正式标准一直是C++98/03[①].相比C++98/03,C++11有了非常多的变化,甚至一度让笔者大呼不认识C++了[②].不过,作为科技行业的从业者,我们要铭记在心的一个铁规就是要拥抱变化.既然我们不认识C++11,那就把它当做一门全新的语言来