Использование "super" в С++

Мой стиль кодирования включает в себя следующую идиому:

class Derived : public Base
{
   public :
      typedef Base super; // note that it could be hidden in
                          // protected/private section, instead

      // Etc.
} ;

Это позволяет мне использовать "супер" в качестве псевдонима для базы, например, в конструкторах:

Derived(int i, int j)
   : super(i), J(j)
{
}

Или даже при вызове метода из базового класса внутри его переопределенной версии:

void Derived::foo()
{
   super::foo() ;

   // ... And then, do something else
}

Он даже может быть привязан (я все равно должен найти это для использования):

class DerivedDerived : public Derived
{
   public :
      typedef Derived super; // note that it could be hidden in
                             // protected/private section, instead

      // Etc.
} ;

void DerivedDerived::bar()
{
   super::bar() ; // will call Derived::bar
   super::super::bar ; // will call Base::bar

   // ... And then, do something else
}

В любом случае, я считаю, что использование "typedef super" очень полезно, например, когда Base является либо подробным, либо/или шаблонизированным.

Дело в том, что super реализован в Java, а также в С# (где он называется "базой", если я не ошибаюсь). Но С++ не хватает этого ключевого слова.

Итак, мои вопросы:

  • Это использование typedef супер общего/редкого/никогда не видел в коде, с которым вы работаете?
  • Это использование typedef super Ok (т.е. вы видите сильные или не очень сильные причины не использовать его)?
  • "супер" должно быть хорошо, если он будет несколько стандартизован в С++, или это использование уже через typedef уже?

Изменить: Родди отметил, что typedef должен быть закрытым. Это означает, что любой производный класс не сможет использовать его без повторного использования. Но я предполагаю, что это также помешало бы супер:: супер цепочку (но кто будет плакать за это?).

Изменить 2: Теперь, спустя несколько месяцев после массового использования "супер", я полностью согласен с точкой зрения Родди: "супер" должен быть закрытым. Я бы дважды отвел его ответ, но я думаю, что не могу.

Ответ 1

Bjarne Stroustrup упоминает в Design and Evolution of С++, что super в качестве ключевого слова был рассмотрен Комитетом по стандарту ISO С++ при первом стандарте С++.

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

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

Майкл Тиманн прибыл поздно, а затем показал, что суперспециализированный супер будет работать отлично, используя ту же технику, о которой спрашивали в этом сообщении.

Итак, нет, это, вероятно, никогда не будет стандартизировано.

Если у вас нет копии, дизайн и эволюция хорошо стоят цены на обложку. Использованные копии могут храниться около $10.

Ответ 2

Я всегда использовал "унаследованный", а не супер. (Возможно, из-за фона Delphi), и я всегда делаю его private, чтобы избежать проблемы, когда "унаследованный" ошибочно исключен из класса, но подкласс пытается его использовать.

class MyClass : public MyBase
{
private:  // Prevents erroneous use by other classes.
  typedef MyBase inherited;
...

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

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

Ответ 3

Одна из проблем заключается в том, что если вы забудете (переопределить) супер для производных классов, тогда любой вызов super:: something будет компилироваться отлично, но, вероятно, не вызовет нужную функцию.

Например:

class Base
{
public:  virtual void foo() { ... }
};

class Derived: public Base
{
public:
    typedef Base super;
    virtual void foo()
    {
        super::foo();   // call superclass implementation

        // do other stuff
        ...
    }
};

class DerivedAgain: public Derived
{
public:
    virtual void foo()
    {
        // Call superclass function
        super::foo();    // oops, calls Base::foo() rather than Derived::foo()

        ...
    }
};

(Как отметил Мартин Йорк в комментариях к этому ответу, эту проблему можно устранить, сделав typedef частным, а не публичным или защищенным.)

Ответ 4

FWIW Microsoft добавила расширение для __ super в своем компиляторе.

Ответ 5

Супер (или унаследованный) - очень хорошая вещь, потому что если вам нужно придерживаться другого слоя наследования между Base и Derived, вам нужно изменить только две вещи: 1. "class Base: foo" и 2. typedef

Если я правильно помню, комитет по стандартам C++ рассматривал возможность добавления ключевого слова для этого... пока Майкл Тиманн не заметил, что этот трюк typedef работает.

Что касается множественного наследования, так как он под управлением программиста вы можете делать все, что хотите: возможно, super1 и super2 или что-то еще.

Ответ 6

Я просто нашел альтернативное обходное решение. У меня большая проблема с подходом typedef, который сегодня меня укусил:

  • Для typedef требуется точная копия имени класса. Если кто-то изменяет имя класса, но не изменяет typedef, тогда вы столкнетесь с проблемами.

Итак, я придумал лучшее решение, используя очень простой шаблон.

template <class C>
struct MakeAlias : C
{ 
    typedef C BaseAlias;
};

Итак, теперь вместо

class Derived : public Base
{
private:
    typedef Base Super;
};

у вас есть

class Derived : public MakeAlias<Base>
{
    // Can refer to Base as BaseAlias here
};

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

Ответ 7

Я не помню этого раньше, но на первый взгляд мне это нравится. Как отмечает Ferruccio, это плохо работает перед лицом ИМ, но MI - это больше исключение, чем правило, и нет ничего, что говорит, что что-то должно использоваться везде чтобы быть полезным.

Ответ 8

Я видел эту идиому, используемую во многих кодах, и я уверен, что даже видел ее где-то в библиотеках Boost. Однако, насколько я помню, наиболее распространенным именем является base (или base) вместо super.

Эта идиома особенно полезна при работе с классами шаблонов. В качестве примера рассмотрим следующий класс (из реального проекта):

template <typename TText, typename TSpec>
class Finder<Index<TText, PizzaChili<TSpec> >, PizzaChiliFinder>
    : public Finder<Index<TText, PizzaChili<TSpec> >, Default>
{
    typedef Finder<Index<TText, PizzaChili<TSpec> >, Default> TBase;
    // …
}

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

Ответ 9

Я довольно часто видел, что он использовался, иногда как super_t, когда базой является сложный тип шаблона (boost::iterator_adaptor делает это, например)

Ответ 10

Это использование typedef супер общего/редкого/никогда не замеченного в коде, с которым вы работаете?

Я никогда не видел этот конкретный шаблон в коде С++, с которым я работаю, но это не значит, что он отсутствует.

это использование typedef super Ok (т.е. вы видите сильные или не очень сильные причины не использовать его)?

Это не допускает множественного наследования (в любом случае, чисто).

должен "супер" быть хорошим, если он будет несколько стандартизован в С++, или это использование уже через typedef уже?

По приведенной выше причине (множественное наследование), нет. Причина, по которой вы видите "супер" на других языках, которые вы указали, заключается в том, что они поддерживают только одно наследование, поэтому нет путаницы в отношении того, что означает "супер" . Конечно, в этих языках это полезно, но на самом деле это не имеет места в модели данных на С++.

О, и FYI: С++/CLI поддерживает эту концепцию в виде ключевого слова "__super". Обратите внимание, однако, что С++/CLI не поддерживает множественное наследование.

Ответ 11

После перехода с Turbo Pascal на С++ в тот же день я использовал это, чтобы иметь эквивалент для ключевого слова "унаследованное" Turbo Pascal, которое работает одинаково. Однако после программирования на С++ в течение нескольких лет я прекратил это делать. Я обнаружил, что мне это очень не нужно.

Ответ 12

Одной из дополнительных причин использования typedef для суперкласса является использование сложных шаблонов в наследовании объектов.

Например:

template <typename T, size_t C, typename U>
class A
{ ... };

template <typename T>
class B : public A<T,99,T>
{ ... };

В классе B было бы идеально иметь typedef для A, иначе вы бы застряли, повторяя его везде, где хотите ссылаться на члены A.

В этих случаях он также может работать с несколькими наследованиями, но у вас не будет typedef с именем "super", он будет называться "base_A_t" или что-то в этом роде.

- jeffk ++

Ответ 13

Я не знаю, редко это или нет, но я, конечно, сделал то же самое.

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

Ответ 14

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

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

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

Ответ 15

Я пытался решить эту ту же проблему; Я бросил несколько идей, таких как использование вариационных шаблонов и расширение пакета, чтобы допускать произвольное число родителей, но я понял, что это приведет к реализации типа "super0" и "super1". Я разгромил его, потому что это было бы более полезно, чем не иметь его для начала.

My Solution включает вспомогательный класс PrimaryParent и реализуется так:

template<typename BaseClass>
class PrimaryParent : virtual public BaseClass
{
protected:
    using super = BaseClass;
public:
    template<typename ...ArgTypes>
    PrimaryParent<BaseClass>(ArgTypes... args) : BaseClass(args...){}
}

Затем какой класс, который вы хотите использовать, будет объявлен как таковой:

class MyObject : public PrimaryParent<SomeBaseClass>
{
public:
    MyObject() : PrimaryParent<SomeBaseClass>(SomeParams) {}
}

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

Причина наследования public BaseClass в PrimaryParent заключается в том, чтобы позволить MyObject иметь полный контроль над наследованием BaseClass, несмотря на наличие между ними класса-помощника.

Это означает, что каждый класс, который вы хотите иметь super, должен использовать вспомогательный класс PrimaryParent, и каждый ребенок может наследовать только один класс с помощью PrimaryParent (отсюда и название).

Другое ограничение для этого метода: MyObject может наследовать только один класс, который наследует от PrimaryParent, и что он должен быть унаследован с помощью PrimaryParent. Вот что я имею в виду:

class SomeOtherBase : public PrimaryParent<Ancestor>{}

class MixinClass {}

//Good
class BaseClass : public PrimaryParent<SomeOtherBase>, public MixinClass
{}


//Not Good (now 'super' is ambiguous)
class MyObject : public PrimaryParent<BaseClass>, public SomeOtherBase{}

//Also Not Good ('super' is again ambiguous)
class MyObject : public PrimaryParent<BaseClass>, public PrimaryParent<SomeOtherBase>{}

Прежде чем отменить это как вариант из-за кажущегося количества ограничений и факта наличия класса среднего человека между каждым наследованием, все это не так.

Множественное наследование - сильный инструмент, но в большинстве случаев будет только один первичный родитель, и если есть другие родители, они, вероятно, будут классами Mixin или классами, которые не наследуются от PrimaryParent в любом случае. Если множественное наследование по-прежнему необходимо (хотя во многих ситуациях полезно использовать композицию для определения объекта вместо наследования), чем просто явно определить super в этом классе и не наследовать от PrimaryParent.

Идея определения super в каждом классе не очень привлекательна для меня, использование PrimaryParent позволяет super, очевидно, основанный на наследовании псевдоним, оставаться в строке определения класса вместо тела класса где данные должны идти.

Это может быть только я.

Конечно, каждая ситуация различна, но рассмотрите эти вещи, которые я сказал при принятии решения о том, какой вариант использовать.

Ответ 17

Это метод, который я использую, который использует макросы вместо typedef. Я знаю, что это не С++-способ делать что-то, но может быть удобно, когда цепочка итераторов объединяется через наследование, когда только базовый класс, расположенный дальше по иерархии, действует на унаследованное смещение.

Например:

// some header.h

#define CLASS some_iterator
#define SUPER_CLASS some_const_iterator
#define SUPER static_cast<SUPER_CLASS&>(*this)

template<typename T>
class CLASS : SUPER_CLASS {
   typedef CLASS<T> class_type;

   class_type& operator++();
};

template<typename T>
typename CLASS<T>::class_type CLASS<T>::operator++(
   int)
{
   class_type copy = *this;

   // Macro
   ++SUPER;

   // vs

   // Typedef
   // super::operator++();

   return copy;
}

#undef CLASS
#undef SUPER_CLASS
#undef SUPER

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

Можно использовать нижний регистр super для репликации поведения, наблюдаемого на Java, но мой стиль кодирования должен использовать все буквы верхнего регистра для макросов.