分解类子集的纯虚函数的实现

Factorizing implementations of pure, virtual functions for a subset of classes

本文关键字:函数 实现 子集 分解      更新时间:2023-10-16

>假设我有这样的类层次结构:

class A {
public:
    virtual void foo(Base *b) = 0;
};
class B {
public:
    virtual void bar() = 0;
};

class C : public A, public B {
public:
    virtual void viz() = 0;
};
class E : public C {
public:
    /** Some non-pure virtual functions, does not implement foo */
};
class F : public E {
    /** does not implement foo */
}
class G : public E {
    /** does not implement foo */
}

现在,我想定义 F 和 G 的派生、"模板化"版本,它声明了一个方法foo(T *s),其中 foo(Base *s) 的实现只是通过应用static_cast调用 foo(T *s(。

此外,我不想向 A 公开模板参数,因为这些接口(A、B、C、E(的用户不应该暴露给模板参数(我正在编写一个插件架构,派生给定接口的用户类型从插件来回传递到基本系统(。

我想定义这个类:

template <typename T>
class GA : public A{
...
}

并像这样定义 F 和 G 的"模板化"推导:

template <typename T>
class GF : public F, public GA {
...
}

问题是 GF 看到了两个不同的 foo(( 声明(一个由 A 给出,另一个由 GA 给出(,所以当我尝试实例化 GF 时,编译器会给我一个错误,因为 foo(Base( 没有定义。

我该如何解决这个问题?

为什么需要多重继承? 为什么不干脆class C : public Impl { ... }

我认为问题是Impl继承了类A,而类C继承了ImplA。这意味着将有两个类A实例,其中第一个 A 的fooImpl 实现,第二个 A 仍然是纯虚拟的。虚拟继承可以解决这个问题,例如:

class A {
public:
    virtual void foo () = 0;
};
class Impl : virtual public A {
public:
    virtual void foo () {}
};
class C
    : virtual public A
    , public Impl
{
};

我会建议一些不同的东西。将该函数声明为纯虚函数,然后通过转发调用在派生类中轻松实现它:

class A {
public:
  virtual void foo() = 0;
};
void A::foo() {}
class B : public A {
   void foo() { A::foo(); }
};

但这并不意味着你不能实现你想要做的事情。如果要在另一个类中实现该操作并使用继承,可以通过两种不同的方式执行此操作:

中间型:

实现提供该功能的中间类型,然后每个派生类型都可以扩展原始A或中间I类型:

class I : public A {
public:
   void foo() {}
};
class B : public I {};      // inherits the implementation
class C : public A {        // needs to provide it's own implementation
   void foo() {}
};

虚拟继承:

或者,您可以使用虚拟继承(在三个选项中,我会避免这种情况(。通过这种方式,您可以创建一个层次结构,其中V虚拟继承A并提供实现。其余的类可以直接从A继承,也可以从虚拟A继承,也可以从V继承。通过使用虚拟继承,可以确保层次结构中存在单个A子对象:

class V : public virtual A {
public:
   void foo() {}
};
class B : public virtual A, V { // Inheritance from V is a detail, can be private
                                // no need to provide foo
};

请注意,对于这个特定的有限问题,这种方法是矫枉过正的,并且它有副作用(对象的布局会有所不同,并且它们的大小会略大(。同样,除非你想混合和匹配来自不同同级类的函数,否则我会避免这种情况。

您的代码中出了什么问题

代码中的问题是您多次从A继承,一次通过D,另一次直接继承。虽然在 D 的路径中,虚函数不再是纯函数,但在来自A的直接路径中,虚函数仍然未定义。

这也会导致错误消息中的第二个问题:歧义。由于有两个A子对象,因此当您尝试对派生最多的类型调用foo时,编译器无法确定要在两个子对象中的哪一个A执行请求。如果尝试从派生最多的类型直接转换为A,则会出现相同类型的歧义问题,因为编译器不知道要引用哪个A

Impl 没有在 D 和 C 中实现 A 的方法,它是一个完全不同的基础。

你应该从 A 派生 Impl,然后从 Impl 而不是 A 派生该子集的每个类。

如果你想同时从 A 和 Impl 继承,那么你需要使用虚拟继承。

编辑:

有关更多详细信息,请参阅@Vlad的答案。