Подтвердить что ты не робот

С++ OO design: Наследование параметра шаблона

У меня есть цепочка наследования с базой, являющейся базовым классом. Я хочу иметь возможность написать шаблон класса, который наследует Base и может использовать другой базовый класс Base. Я мог бы использовать виртуальное наследование, но нашел другое решение. Я хотел бы знать, если это общий/значительный/законный дизайн класса:

Напишите шаблон класса, в котором параметром шаблона является его класс, т.е. он должен быть базовым или базовым классом. В конструкторе я могу использовать static assert, чтобы действительно убедиться, что пользователь не использовал какой-либо незаконный класс в качестве параметра шаблона.

Если это сработает, у меня никогда не будет проблем с виртуальным наследованием... вопрос в том, что это нормально. Я никогда не видел его в других проектах, поэтому я хочу убедиться, прежде чем использовать его.

РЕДАКТИРОВАТЬ. Чтобы быть уверенным, что я не смущаю вас, вот какой-то код:

class Base
{
};

class Derived : public Base
{
};

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

Теперь я могу использовать Base или любой класс Base -derived, например. Derived, как параметр TheBase.

4b9b3361

Ответ 1

Это действительный шаблон проектирования. Это наследование mixin, а не CRTP. Наследование миксинов обеспечивает способ безопасного имитации множественного наследования программистом, вручную линеаризующим иерархию наследования. Шаблонные классы - это микшины. Если вы хотите расширить класс с несколькими миксинами, вам необходимо определить порядок композиции, например Big<Friendly<Dog> >. Программирование Mixin на С++ описано в этой статье Dr Dobb. Mixins можно использовать для реализации статической версии шаблона декоратора GoF, как описано здесь. Микшины играют роль simlar в С++, которые черты (а не С++-черты) играют в Scala и SmallTalk.

В CRTP это базовый класс, который является шаблоном:

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

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

Ответ 2

Позднее редактирование: через год я пересматриваю свой собственный ответ. Я изначально ошибочно заявил, что шаблон OP размещен CRTP. Это неверно. Это действительно смесь, пожалуйста, прочитайте Даниэль Малер ниже на странице для правильного объяснения.

Оригинал: Это нормально использовать такой дизайн. WTL использует его, например. Он используется для реализации Статический полиморфизм и называется Любопытно повторяющийся шаблон шаблона

Ответ 3

Это прекрасно, как указывает Задирион. Причина, по которой он работает (упрощен), заключается в том, что шаблоны на С++, в отличие от generics на С#, являются компиляцией. Было бы ошибкой сказать "это typedef", и я получил бы много хлопка для него, но пусть все будет просто и сказать, что это так.

Рассмотрим:

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

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

и позже:

super<base> s;

Абсолютно хорошо. На самом деле это довольно красивая конструкция. Поскольку это время компиляции, вы можете выбрать свой базовый класс, который в некоторых дизайнерских идиомах может быть очень благоприятным.

Ответ 4

Вот хороший девиз: Использовать шаблоны для типов, но наследование для поведения.

Придерживайтесь. Конечно, есть много коротких сокращений/трюков, которые вы могли бы использовать для выполнения этой работы, но в конечном итоге эти плохие варианты дизайна будут головными болями. Если вы хотите использовать их, обязательно изучите преимущества и недостатки.

Теперь, возвращаясь к вашему вопросу, вы можете сделать следующее: см. CRTP и Статический полиморфизм.

Ответ 6

То, что вы пытаетесь сделать, это наследовать от двух классов, которые могут иметь общий базовый класс, это правильно? В этом случае вы столкнетесь с проблемами виртуального наследования (т.е. Вам придется объявить виртуальным наследование базового класса для обоих интересующих вас классов). Это может привести к незначительным (вероятно, незначительным) издержкам из-за некоторой поддержки во время выполнения (2 vpointers).

Ваш код не является CRTP (в CRTP базовый класс является шаблоном, получающим производный класс) и, похоже, не затрагивает проблему двойного наследования, которую вы пытались избавиться.

Насколько я могу это видеть, вы можете либо принять виртуальное наследование, либо использовать ключевое слово virtual, выполняющее минимальные накладные расходы, или вы можете реорганизовать свой код.

Я не совсем понял, что вы пытаетесь сделать, но если вы пытаетесь наследовать от двух разных классов с общим базовым классом (это виртуальное наследование - это все), и по какой-то причине вы не хотите для использования ключевого слова 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 может в конечном итоге решить ваши проблемы.

В вашем конкретном случае ваш подход работает, как отмечают другие, который использовался во многих приложениях, но я не думаю, что он может эффективно решить проблему двойного наследования. В конечном итоге только конкретный проект может привести к решению меньшего зла.