14

我有一个继承链,Base 是基类。我希望能够编写一个继承 Base 和可能的另一个 Base 派生类的类模板。我可以使用虚拟继承,但我找到了另一种解决方案。我想知道它是否是常见的/可观的/合法的类设计:

编写一个类模板,其中模板参数是它派生的类,即它必须是 Base 或 Base 派生类。在构造函数中,我可以使用静态断言来真正确保用户没有使用任何非法类作为模板参数。

如果它有效,我将永远不会有虚拟继承问题......问题是,它可以这样做。我在其他项目中从未见过它,所以我想在使用它之前先确定一下。

编辑:为了确保我不会混淆你,这里有一些代码:

class Base
{
};

class Derived : public Base
{
};

template <Class TheBase>
class MyDerived : public TheBase
{
};

现在我可以使用Base或任何Base派生类,例如Derived,作为TheBase参数。

4

6 回答 6

23

这是一个有效的设计模式。它是 mixin 继承,而不是 CRTP。Mixin 继承提供了一种通过程序员手动线性化继承层次结构来安全地模拟多重继承的方法。模板化的类是 mixins。如果你想用多个 mixin 扩展一个类,你必须决定组合的顺序,比如Big<Friendly<Dog> >. 这篇Dobb 博士的文章中描述了 C++ 中的 Mixin 编程。Mixin 可用于实现 GoF 装饰器模式的静态版本,如此处所述。Mixin 在 C++ 中扮演的角色与 Scala 和 SmallTalk 中的特征(不是 C++ 特征)类似。

CRTP中,它是作为模板的基类:

template <class Param>
class Base { ... };

class Derived : public Base<Derived> { ... };
于 2014-01-29T07:31:27.953 回答
10

稍后编辑:一年后,我在这里修改自己的答案。我最初错误地指出,OP 发布的模式是 CRTP。这是不正确的。它确实是一个 mixin,请阅读页面下方 Daniel Mahler 的答案以获得正确的解释。

原文:用这样的设计是可以的。WTL以它为例。它用于实现静态多态性,被称为Curiously recurring template pattern

于 2013-03-20T17:44:32.350 回答
8

这很好,正如 Zadirion 指出的那样。它起作用(简化)的原因是 C++ 中的模板与 C# 中的泛型不同,是编译时的。说“这是一个 typedef”是我的疏忽,我会为此受到很多批评,但让我们保持简单,说它是。

考虑:

class base {
protected:
    base() { };
    virtual ~base() { };
};

template<class T>
class super : public T {
};

然后:

super<base> s;

绝对没问题。这实际上是一个相当漂亮的结构。因为是编译时,你可以选择你的基类,这在某些设计习惯中可能是非常有利的。

于 2013-03-20T17:47:58.313 回答
5

这是一个很好的座右铭:对类型使用模板,但对行为使用继承。

坚持下去。当然,您可能会使用很多捷径/技巧来完成工作,但从长远来看,这些糟糕的设计选择会让人头疼。如果您想使用此类,请务必研究其优点和缺点。

现在,回到您的问题,您所问的可以做到:请参阅CRTPStatic polymorphism

于 2013-03-20T17:47:35.053 回答
4

听起来您在谈论Curiously Recurring Template Pattern,它是有效的 C++。

于 2013-03-20T17:46:22.847 回答
1

您要做的是从两个可能具有共同基类的类继承,对吗?在这种情况下,您应该处理虚拟继承问题(即,您必须将您感兴趣的两个类的基类继承声明为虚拟)。由于一些运行时支持(更多 2 个 vpointer),这只会导致很小(可能微不足道)的开销。

您的代码不是 CRTP(在 CRTP 中,基类是接收派生类的模板),并且似乎没有以任何方式解决您试图摆脱的双重继承问题。

据我所知,您可以接受虚拟继承并使用虚拟关键字以最小的开销产生,或者您可以重构您的代码。

我不完全理解您要做什么,但是如果您尝试从具有公共基类的两个不同类继承(虚拟继承就是这样)并且由于某种原因您不想使用virtual关键字,那么您可以按以下方式使用 CRTP:

#include <iostream>
using namespace std;

template<class Derived>
class Base
{
public:
    void basefunc() { cout << "base here"<< endl; }
    virtual void polyfunc() { cout << "base poly here"<< endl; }
};

class Derived : public Base<Derived>
{
public:
    void derivedfunc() { cout << "derived here"<< endl; }
    virtual void polyfunc() { cout << "derived poly here"<< endl; }
};

class OtherDerived : public Base<OtherDerived>
{
public:
    void otherderivedfunc() { cout << "otherderived here"<< endl; }
    virtual void polyfunc() { cout << "otherderived poly here"<< endl; }
};

class InheritingFromBoth : public Derived, public OtherDerived
{
public:
    void inheritingfunc() { cout << "inheritingfromboth here" << endl; }
    virtual void polyfunc() { cout << "inheritingfromboth poly here"<< endl; }  
};

int main() {

    Derived obj;
    OtherDerived obj2;

    InheritingFromBoth *obj3 = new InheritingFromBoth();
    Derived *der = dynamic_cast<Derived*>(obj3);
    der->polyfunc();
    OtherDerived *der2 = dynamic_cast<OtherDerived*>(obj3);
    der2->polyfunc();

    Base<Derived>* bptr = dynamic_cast<Base<Derived>*>(obj3);
    bptr->polyfunc();
    Base<OtherDerived>* bptr2 = dynamic_cast<Base<OtherDerived>*>(obj3);
    bptr2->polyfunc();



    return 0;
}

通过创建基类的两个不同实例,您将避免继承歧义。

如果您打算同时从基类和基派生类继承,一个更简单、或许更清洁、更好的解决方案如下:

  • 从类 Base从 Base 派生类继承让我认为您希望能够同时访问 Base 方法和 Base 派生方法..

如果您在类设计中注意潜在的名称隐藏 问题,并且只是使用多态性来“定制”您真正想要不同的函数的行为(并且可以进行强制转换控制),那么Base -> Derived -> YourClass最终可以解决一个干净的层次结构你的问题。

在您的特定情况下,您的方法有效,正如其他人在许多应用程序中使用的那样,但我认为不能有效地解决您的双重继承问题。最终,只有特定的设计案例才能导致邪恶程度较低的解决方案。

于 2014-01-30T23:46:02.323 回答