从两个派生类进行多重继承

29

我有一个充当接口的抽象基类。

我有两个“集合”派生类,它们实现了抽象类的一半。(一个“集合”定义与初始化相关的抽象虚拟方法,另一个“集合”定义与实际工作相关的方法。)

然后我有派生类,使用多重继承构建完全定义的类(并且本身不添加任何内容)。

因此:(错误的伪代码)

class AbsBase {
  virtual void init() = 0;
  virtual void work() = 0;
}

class AbsInit : public AbsBase {
  void init() { do_this(); }
  // work() still abs
}

class AbsWork : public AbsBase {
  void work() { do_this(); }
  // init() still abs
}

class NotAbsTotal : public AbsInit, public AbsWork {
  // Nothing, both should be defined
}

首先,我能这样做吗?我可以从两个都派生自同一基类的类中继承吗?(希望可以)。

不过这里有一个“真正的问题”(我在上面说了谎以简化示例)。

我实际上添加了非抽象访问器方法到基类:

class AbsBase {
public:
  void init() { init_impl(); }
  void work() { work_impl(); }

private:
  virtual void init_impl() = 0;
  virtual void work_impl() = 0;
}

因为一个常见的惯用语是将所有虚方法设为私有。

不幸的是,现在AbsInit和AbsWork都继承了这些方法,因此NotAbsTotal继承了“两个(被重载的)函数”(我意识到我可能在编译时在捣鼓什么)。

无论如何,当尝试使用该类时,g ++会报告:“request for member init() is ambiguous”。

我认为,如果我将我的AbsBase类用作纯接口,则可以避免这种情况(假设顶部示例有效)。

所以: - 我的实现是否完全错误? - 将虚方法设置为私有的惯用语是否存在限制? - 如何重构我的代码以实现我的目标?(提供一个公共接口,但允许一种方式来交换“一组”成员函数的实现)

编辑:

看起来我不是第一个遇到这个问题的人: http://en.wikipedia.org/wiki/Diamond_problem

似乎虚继承是这里的解决方案。我之前听说过虚继承,但还没理解透彻。我仍然愿意听取建议。

5个回答

38

看起来你想使用虚继承。虚继承是否是个好主意另当别论,但下面是实现它的方法:


class AbsBase {...};
class AbsInit: public virtual AbsBase {...};
class AbsWork: public virtual AbsBase {...};
class NotAbsTotal: public AbsInit, public AbsWork {...};

基本上,默认的非虚拟多继承将在派生类中包含每个基类的一个副本,并包含它们所有的方法。这就是为什么你有两个AbsBase的副本,以及为什么你的方法使用是不明确的原因,因为两套方法都被加载了,所以C++无法知道要访问哪个副本!

虚拟继承将所有对虚拟基类的引用压缩成一个数据结构。这应该使得从基类继承的方法再次变得明确。然而,请注意:如果两个中间类中有额外的数据,可能会有一些小的运行时开销,以便使代码能够找到共享的虚拟基类。


1
你为什么这么说?除了语法复杂性之外,虚拟继承还有哪些影响? - mmocny
将基指针转换为派生类通常涉及指针调整,使用动态查找表。这种额外的间接性引入了从基类到派生类的转换成本。在调用虚方法时,这种开销在调用中是微不足道的。 - Martin v. Löwis

1

虽然这可能让大多数人感到不安,但它是可以完成的。

您需要使用“虚拟继承”,其语法类似于

class AbsInit: public virtual AbsBase {...};
class AbsWork: public virtual AbsBase {...};
class NotAbsTotal: public AbsInit, public AbsWork {...};

然后您需要指定要使用的函数:

NotAbsTotal::work()
{
    AbsInit::work_impl();
}

(已更新为正确的语法)


1
你需要将继承声明为虚拟的:
struct AbsBase {
          virtual void init() = 0;
          virtual void work() = 0;
};

struct AbsInit : virtual public AbsBase {
          void init() {  }
};

struct AbsWork : virtual public AbsBase {
          void work() { }
};

struct NotAbsTotal : virtual public AbsInit, virtual public AbsWork {
};

void f(NotAbsTotal *p)
{
        p->init();
}

NotAbsTotal x;

这个很好运行,即使我的非虚拟公共方法。然而,我将不得不尝试一下,看看使用指向派生类型对象的基类型指针时会发生什么,以确保虚继承像虚函数重载一样工作 :) - mmocny
在从虚基类转换为派生类时,您总是需要使用dynamic_cast<>,这又要求在虚基类中定义一个虚方法。普通使用虚基类不应该有问题 - 它将以多态方式访问派生类。 - Martin v. Löwis

0

我在下面的链接中找到了一个好的简单示例。该文章通过一个计算矩形面积和周长的示例程序进行解释。你可以去看看,祝贺。

多级继承是一种继承层次结构,其中一个派生类从多个基类继承。了解更多...

http://www.mobihackman.in/2013/09/multiple-inheritance-example.html


0

你必须开始从所要建模的角度思考问题。

公共继承应该只用于建立“是一个”关系,例如狗是一种动物,正方形是一种形状等。

可以参考Scott Meyer的书《Effective C++》,里面有一篇关于面向对象设计各个方面应如何解释的优秀文章。

编辑:我忘了说,虽然目前为止提供的答案在技术上是正确的,但我认为它们没有解决你试图建模的问题,而这正是你问题的关键所在!

希望对你有所帮助。

祝好,

罗布


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接