在这种情况下,您能建议一个好的多重继承替代方案吗?

Can you suggest an good alternative for multiple inheritance in this case?

总结:下面的案例涉及使用多重继承来继承扩展接口和基本接口的实现。

我使用虚拟多重继承建立了以下系统:

我有一个抽象接口层次结构:

        ICommon                         
       /       \
ISpecific1     ISpecific2

正如预期的那样,特定接口在派生自 ICommon 的通用功能之上添加了功能。

我还有 类 实现接口:

应用程序最终只使用了 SpecificImp1 和 SpecificImp2。 但是需要 CommonImp 以避免实现 ICommon 两次(在 SpecificImp1、SpecificImp2 中)。

这意味着,例如,SpecificImp1 需要为它需要公开的整个接口继承 ISpecific1,并为公共部分的实现继承 CommonImp。 但是ISpecific1和CommonImp都继承了ICommon(一个为了扩展接口,一个为了实现)。 这使得 SpecificImp1 间接继承了 ICommon 两次。 使用虚拟继承解决了菱形继承问题。

这是最小的可重现代码示例:

#include <iostream>


struct ICommon
{
    virtual void DoCommon() = 0;
};

struct ISpecific1 : public virtual ICommon
{
    virtual void DoSpecific1() = 0;
};

struct ISpecific2 : public virtual ICommon
{
    virtual void DoSpecific2() = 0;
};



struct CommonImp : public virtual ICommon
{
    virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};

struct SpecificImp1 : public ISpecific1, public CommonImp
{
    virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};

struct SpecificImp2 : public ISpecific2, public CommonImp
{
    virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};



int main()
{
    SpecificImp1 s1;
    s1.DoCommon();
    s1.DoSpecific1();

    SpecificImp2 s2;
    s2.DoCommon();
    s2.DoSpecific2();

    return 0;
}

到目前为止,这个设计对我来说很好,但它很麻烦。 多重继承始终是您应该考虑的替代方案。

所以,我的问题是,鉴于这个系统,你能推荐一个好的替代方案吗?

顺便说一句 - 以上所有类型都是包含所有内容的结构 public。这样做是为了让代码更短,请忽略。

这是我从评论中得到的想法的总结,以防对任何人有用。

有一些替代方法可以让我摆脱多重继承:

  1. 最简单的选择: SpecificImp1SpecificImp2 将仅继承 ISpecific1,并且他们将保留 CommonImp 作为成员。 然后他们将通过委托给 CommonImp 成员来实现 ICommon 的所有方法。

    唯一的缺点是需要在 SpecificImp1SpecificImp2 中复制 ICommon 实现 (虽然这是一个微不足道的重复 - 只是转发给成员)。

  2. 使用type-erasure技术实现无继承多态

    这里有一些例子:C++ 'Type Erasure' Explained

    一个有趣的视频:polymorphism without inheritance

    缺点是构建代码很多。

  3. 其他基于模板的技术。

    也许使用 CRTP 模式 (Curiously recurring template pattern), 实现某种编译时多态性。

    这个方向我还要考虑

谢谢你的想法。

作为对您答案中第 1 点的修改,您不必使 CommonImp 成为成员,也不必复制任何内容:

#include <iostream>
#include <vector>


struct ICommon
{
    virtual void DoCommon() = 0;
};

struct ISpecific1
{
    virtual void DoSpecific1() = 0;
};

struct ISpecific2
{
    virtual void DoSpecific2() = 0;
};



struct CommonImp : public ICommon
{
    virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};

struct SpecificImp1 : public ISpecific1, public CommonImp
{
    virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};

struct SpecificImp2 : public ISpecific2, public CommonImp
{
    virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};


int main()
{
    SpecificImp1 si1;
    SpecificImp2 si2;
    si1.DoCommon(); // 1.
    si2.DoCommon(); // 2.

    { // begin new scope to make sure the addresses stay valid
        std::vector<ICommon*> imps;
        imps.push_back(&si1); // 3.
        imps.push_back(&si2); // 4.
    }
    return 0;
}

这解决了一般要求。没有针对所需特定接口的虚拟继承,没有钻石继承。

但是,在上面的代码中,具体的实现也必须提供ICommon接口,并不是由具体的接口强制执行的。但它是通过各种用法间接强制执行的:通过在 1. 和 2. 中调用 DoCommon() 并在 3. 和 4. 中将特定对象的地址 ob 转换为 ICommon*。所以最简单的解决方案是就这样吧。

我们可以(在一定程度上)通过要求成员函数 getICommonP,甚至在特定的 接口 中强制执行 ICommon 的继承,returns 是一个 ICommon 指针(见下面的代码)。从 ICommon 继承的具体实现只是 return this。如果未实现此抽象成员函数,编译器将抛出错误。此函数的额外好处是可以将特定接口转换为公共接口(5. 和 6.)。具体实现可以直接转换(3.和4.),因为继承自ICommon。请参阅标有“//已添加”的行。

#include <iostream>
#include <vector>


struct ICommon
{
    virtual void DoCommon() = 0;
};

struct ISpecific1
{
    virtual ICommon* getICommonP() = 0; // added
    virtual void DoSpecific1() = 0;
};

struct ISpecific2
{
    virtual ICommon* getICommonP() = 0; // added
    virtual void DoSpecific2() = 0;
};



struct CommonImp : public ICommon
{
    virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};

struct SpecificImp1 : public ISpecific1, public CommonImp
{
    virtual ICommon* getICommonP() override { return this; } // added
    virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};

struct SpecificImp2 : public ISpecific2, public CommonImp
{
    virtual ICommon* getICommonP() override { return this; } // added
    virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};


int main()
{
    SpecificImp1 si1;
    SpecificImp2 si2;
    {
        std::vector<ICommon*> imps;
        imps.push_back(&si1); // 3.
        imps.push_back(&si2); // 4.
        ISpecific1* si1p = &si1;
        ISpecific2* si2p = &si2;
        imps.push_back(si1->getICommonP()); // added 5.
        imps.push_back(si2->getICommonP()); // added 6.
    }
    return 0;
}

更自然的成员函数是接口中的转换运算符 virtual operator ICommon&() 而不是 virtual ICommon* getICommonP(),但是当您的实现也继承自相同的 class 时,一些编译器会发出警告],因为在这种情况下,对基 classes 的引用的转换是自动完成的,无需调用显式转换运算符成员函数。

附带说明:特定接口与您答案中第 1 点的解决方案兼容(公共接口转发除外):而不是指向 class 本身的指针('this' ) getICommonP() 函数将 return 指向 ICommonImp 成员的指针。实现可以决定如何实现和提供通用接口——从它继承或将通用实现保留为成员变量。

如果您不喜欢添加的成员函数,在特定接口中强制从接口 ICommon 继承特定实现的替代方法是将特定接口制作成模板并从它们继承:

struct SpecificImp2 : public ISpecific2<SpecificImp2>, public CommonImp

然后template<class T> Ispecific2<T>可以测试(或要求)T是否继承自ICommonISpecific2<T>

这将进入您答案中第 3 点的方向。

因为你现在没有一个 Ispecific2,而是很多 ISpecific2<>(因为它是一个模板)——而且你经常需要一个界面,你可以拥有所有的模板 classes ISpecific2Templ<> 继承自相同的实际特定接口 ISpecific2