c++ OO设计:模板参数的继承

C++ OO design: Inheritance of template parameter

本文关键字:参数 继承 OO 设计 c++      更新时间:2023-10-16

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

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

如果它工作,我将永远不会有虚拟继承问题…问题是,这样做可以吗?我从来没有在其他项目中看到过它,所以我想在使用它之前确定一下。

EDIT:只是为了确保我没有混淆你,这里有一些代码:

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

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

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

在CRTP中,基类是模板:

template <class Param>
class Base { ... };
class Derived : public Base<Derived> { ... };

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

使用这样的设计是可以的。例如,WTL使用它。它用于实现静态多态性,并被称为好奇循环模板模式

这很好,正如Zadirion指出的那样。它起作用的原因(简化)是c++中的模板,不像c#中的泛型,是编译时的。如果我说"这是一个类型定义",那将是我的疏忽,我也会因此受到很多批评,但让我们简单地说,它是。

考虑:

class base {
protected:
    base() { };
    virtual ~base() { };
};
template<class T>
class super : public T {
};

,后来:

super<base> s;

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

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

坚持下去。当然,你可以使用许多捷径/技巧来完成工作,但从长远来看,这些糟糕的设计选择将令人头疼。如果你想使用这种方法,一定要研究它的优点和缺点。

现在,回到你的问题,你所问的是可能做到的:参见CRTP和静态多态性。

听起来你在谈论奇怪的循环模板模式,这是有效的c++。

你要做的是继承两个类,这可能有一个共同的基类,是正确的吗?在这种情况下,你应该处理虚拟继承问题(也就是说,你必须将你感兴趣的两个类的基类继承声明为virtual)。由于一些运行时支持(多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 -> Derived -> YourClass这样的干净层次结构最终可以解决您的问题。

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