Używanie "super" w C++

Mój styl kodowania zawiera następujący idiom:

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

      // Etc.
} ;

To pozwala mi używać "super" jako aliasu do bazowania, na przykład, w konstruktorach:

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

Lub nawet podczas wywoływania metody z klasy bazowej wewnątrz jej nadpisanej wersji:

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

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

Może być nawet przykuta (muszę jeszcze znaleźć do tego zastosowanie):

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
}

W każdym razie, uważam użycie "typedef super" za bardzo przydatne, na przykład, gdy baza jest albo słowna i/lub szablon.

Faktem jest, że super jest zaimplementowany zarówno w Javie, jak i w C# (gdzie nazywa się "base", chyba że się mylę). Ale C++ nie ma tego słowa kluczowego.

Moje pytania:
  • czy użycie typedef jest bardzo powszechne/rzadkie / nigdy nie widziane w kodzie, z którym pracujesz?
  • czy użycie typedef jest super Ok (tzn. czy widzisz silne czy nie tak silne powody, aby go nie używać)?
  • czy "super" powinno być dobre, czy powinno być nieco ustandaryzowane w C++, czy jest to użycie wystarczy już wpis na maszynie?

Edit: Roddy wspomniał, że typedef powinien być prywatny. Oznaczałoby to, że każda klasa pochodna nie byłaby w stanie jej użyć bez ponownego zgłoszenia jej. Ale chyba też zapobiegłoby to super:: super chaining (ale kto będzie za to płakał?).

Edit 2: teraz, kilka miesięcy po masowym użyciu "super", z całego serca zgadzam się z punktem widzenia Roddy ' ego: "super" powinno być prywatne. Dwa razy podniosłbym jego odpowiedź, ale chyba nie mogę.

Author: paercebal, 2008-10-07

18 answers

Bjarne Stroustrup wspomina w Design and Evolution of C++, że super jako słowo kluczowe zostało uznane przez Komitet Normalizacyjny ISO C++ za pierwszy raz, gdy C++ został znormalizowany.

Dag Bruck zaproponował to rozszerzenie, nazywając klasę bazową " dziedziczoną."We wniosku wspomniano o kwestii wielokrotnego dziedziczenia i oznaczałoby to niejednoznaczne zastosowania. Nawet Stroustrup był przekonany.

Po dyskusji Dag Bruck (tak, ta sama osoba składająca propozycję) napisał, że propozycja była możliwa do wdrożenia, sprawna technicznie i wolna od poważnych wad, a także obsługiwała wiele dziedziczeń. Z drugiej strony, nie było wystarczająco dużo huku dla Dolca, a Komisja powinna zająć się trudniejszym problemem.

Michael Tiemann przybył późno, a następnie pokazał, że typedef ' ed super będzie działać dobrze, przy użyciu tej samej techniki, o którą pytano w tym poście.

Więc, nie, to prawdopodobnie nigdy nie zostanie znormalizowane.

Jeśli nie masz kopii, Projekt i Evolution jest warte ceny okładki. Zużyte egzemplarze można mieć za około 10 dolarów.

 160
Author: Max Lybbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-13 16:56:25

Zawsze używałem "odziedziczone", a nie super. (Prawdopodobnie ze względu na tło Delphi), i zawsze robię to private, aby uniknąć problemu, gdy 'dziedziczona' jest błędnie pominięta w klasie, ale podklasa próbuje jej użyć.

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

Mój standardowy 'szablon kodu' do tworzenia nowych klas zawiera typedef, więc nie mam okazji przypadkowo go pominąć.

Nie sądzę, aby przykuta sugestia "super:: super" była dobrym pomysłem - jeśli to robisz, jesteś prawdopodobnie bardzo mocno przywiązany do określonej hierarchii, a zmiana jej prawdopodobnie zepsuje coś źle.

 107
Author: Roddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 08:57:07

Problem polega na tym, że jeśli zapomnisz (ponownie)zdefiniować super dla klas pochodnych, to każde wywołanie Super::coś będzie dobrze skompilowane, ale prawdopodobnie nie wywoła żądanej funkcji.

Na przykład:

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()

        ...
    }
};

(Jak zauważył Martin York w komentarzach do tej odpowiedzi, problem ten można wyeliminować, czyniąc typedef prywatnym, a nie publicznym lub chronionym.)

 37
Author: Kristopher Johnson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 01:27:38

Fwiw Microsoft dodał rozszerzenie dla _ _ super w swoim kompilatorze.

 21
Author: krujos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 22:51:55

Super (lub dziedziczona) jest bardzo dobrą rzeczą, ponieważ jeśli chcesz przykleić kolejną warstwę dziedziczenia między bazą a pochodną, musisz tylko zmienić dwie rzeczy: 1. "class Base: foo" i 2. typedef

Jeśli dobrze pamiętam, Komitet Standardów C++ rozważał dodanie do tego słowa kluczowego... dopóki Michael Tiemann nie zauważył, że ta sztuczka na maszynie działa.

Co do dziedziczenia wielokrotnego, skoro jest pod kontrolą programisty możesz robić co chcesz: może super1 i super2, czy co tam.

 15
Author: Colin Jensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 22:00:46

Nie przypominam sobie, żebym widział to wcześniej, ale na pierwszy rzut oka mi się to podoba. Jak zauważa Ferruccio, to nie działa dobrze w obliczu MI, ale MI jest bardziej wyjątkiem niż regułą i nie ma nic, co mówi, że coś musi być użytecznego wszędzie, aby być użytecznym.

 13
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:26

Znalazłem alternatywne obejście. Mam duży problem z podejściem typedef, które mnie dzisiaj ugryzło:

  • typedef wymaga dokładnej kopii nazwy klasy. Jeśli ktoś zmieni nazwę klasy, ale nie zmieni typedef, pojawią się problemy.

Więc wymyśliłem lepsze rozwiązanie, używając bardzo prostego szablonu.

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

Więc teraz, zamiast

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

Masz

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

W tym przypadku, BaseAlias nie jest prywatne i próbowałem aby uchronić się przed nieostrożnym użyciem, wybierając nazwę typu, która powinna ostrzec innych programistów.

 13
Author: paperjam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-11 10:38:20

Widziałem ten idiom w wielu kodach i jestem prawie pewien, że widziałem go gdzieś w bibliotekach Boosta. Jednak, o ile pamiętam, najczęściej spotykaną nazwą jest base (lub Base) zamiast super.

Ten idiom jest szczególnie przydatny przy pracy z klasami szablonów. Jako przykład, rozważmy następującą klasę (z prawdziwego projektu):

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;
    // …
}
Nie przejmuj się śmiesznymi imionami. Ważnym punktem jest to, że łańcuch dziedziczenia używa argumentów typu, aby osiągnąć polimorfizm w czasie kompilacji. Niestety poziom zagnieżdżania tych szablonów staje się dość wysoki. Dlatego skróty mają kluczowe znaczenie dla czytelności i konserwacji.
 9
Author: Konrad Rudolph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 09:17:16

Dość często widziałem go używanego, czasami jako super_t, gdy baza jest złożonym typem szablonu (boost::iterator_adaptor robi to na przykład)

 4
Author: James Hopkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 21:52:59

czy jest to użycie typedef super common / rare / never seen in the code you work with?

Nigdy nie widziałem tego konkretnego wzorca w kodzie C++, z którym pracuję, ale to nie znaczy, że go nie ma.

czy użycie typedef jest super Ok (tzn. czy widzisz silne czy nie tak silne powody, aby go nie używać)?

Nie pozwala na wielokrotne dziedziczenie(w każdym razie czysto).

Czy "super" powinno być dobre, czy powinno być nieco znormalizowane w C++, a może to użycie przez typedef już wystarczy?

Z wyżej przytoczonego powodu (dziedziczenie wielokrotne), nie Powodem, dla którego widzisz "super" w innych wymienionych językach, jest to, że obsługują one tylko pojedyncze dziedziczenie, więc nie ma wątpliwości, co do tego, do czego odnosi się "super". Oczywiście, w tych językach jest to przydatne, ale tak naprawdę nie ma miejsca w modelu danych C++.

Oh, I FYI: C++ / CLI wspiera tę koncepcję w postaci słowa kluczowego "__super". Proszę. zauważ jednak, że C++ / CLI również nie obsługuje dziedziczenia wielokrotnego.

 4
Author: Toji,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 21:59:53

Dodatkowym powodem, dla którego należy użyć typedef dla superclass, jest używanie złożonych szablonów w dziedziczeniu obiektu.

Na przykład:

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

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

W klasie B byłoby idealne, aby mieć typedef dla A, w przeciwnym razie utkniesz powtarzając go wszędzie, gdzie chcesz odwołać się do członków A.

W tych przypadkach może działać również z wieloma dziedziczeniami, ale nie miałbyś typedef o nazwie 'super', nazywałby się 'base_A_t' lub coś w tym stylu to.

--jeffk++

 3
Author: jdkoftinoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 22:22:43

Po migracji z Turbo Pascala do C++ z powrotem w dzień, robiłem to, aby mieć odpowiednik dla Turbo Pascala "dziedziczone" słowo kluczowe, które działa w ten sam sposób. Jednak po programowaniu w c++ na kilka lat przestałem to robić. Okazało się, że po prostu nie potrzebuję go bardzo.

 2
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 21:56:49

Nie wiem, czy to rzadkie, czy nie, ale na pewno zrobiłem to samo.

Jak już wspomniano, trudność w tworzeniu tej części języka polega na tym, że Klasa korzysta z dziedziczenia wielokrotnego.

 1
Author: Matt Dillard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 21:55:39

Używam tego od czasu do czasu. Kiedy tylko znajdę się wpisując typ klasy podstawowej kilka razy, zastąpię go z typedef podobny do twojego.

Myślę, że może się przydać. Jak mówisz, jeśli twoja klasa bazowa jest szablonem, może zapisać typowanie. Ponadto klasy szablonów mogą przyjmować argumenty, które działają jako zasady działania szablonu. Możesz dowolnie zmieniać typ bazy bez konieczności poprawiania wszystkich odniesień do niej tak długo, jak długo pozostaje interfejs bazy kompatybilny.

Myślę, że użycie przez typedef już wystarczy. I tak nie widzę jak by to było wbudowane w Język, ponieważ wiele dziedziczeń oznacza, że może być wiele klas bazowych, więc możesz wpisać je tak, jak uważasz za odpowiednie dla klasy, którą logicznie uważasz za najważniejszą klasę bazową.

 1
Author: Scott Langham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 21:57:45

Próbowałem rozwiązać ten sam problem; rzuciłem kilka pomysłów, takich jak użycie różnych szablonów i rozszerzenie pakietu, aby umożliwić dowolną liczbę rodziców, ale zdałem sobie sprawę, że spowoduje to implementację jak 'super0' i 'super1'. Zniszczyłem go, bo to byłoby ledwie bardziej przydatne niż nie posiadanie go na początku.

Moje rozwiązanie zawiera klasę pomocniczą PrimaryParent i jest zaimplementowane tak:

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

Wtedy, które klasy chcesz użyć będzie deklarowane jako takie:

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

Aby uniknąć konieczności używania dziedziczenia wirtualnego w PrimaryParent na BaseClass, konstruktor przyjmujący zmienną liczbę argumentów jest używany do umożliwienia budowy BaseClass.

Powodem dziedziczenia public BaseClass do PrimaryParent jest umożliwienie MyObject pełnej kontroli nad dziedziczeniem BaseClass pomimo posiadania klasy pomocniczej między nimi.

Oznacza to, że każda klasa, którą chcesz mieć super musi używać klasy pomocniczej PrimaryParent, a każde dziecko może dziedziczyć tylko z jednej klasy używając PrimaryParent (stąd nazwa).

Innym ograniczeniem tej metody jest MyObject może dziedziczyć tylko jedną klasę, która dziedziczy z PrimaryParent, a ta musi być dziedziczona za pomocą PrimaryParent. Oto co mam na myśli:

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>{}

Zanim odrzucisz to jako opcję ze względu na pozorną liczbę ograniczeń i fakt, że pomiędzy każdym dziedziczeniem jest klasa średnia, te rzeczy nie są złe.

Dziedziczenie wielokrotne jest silnym narzędziem, ale w w większości przypadków będzie tylko jeden rodzic podstawowy, a jeśli są inni rodzice, prawdopodobnie będą to Klasy Mixin, lub klasy, które nie dziedziczą po PrimaryParent. Jeśli dziedziczenie wielokrotne jest nadal konieczne (choć w wielu sytuacjach korzystniej byłoby użyć kompozycji do zdefiniowania obiektu zamiast dziedziczenia), to tylko jawnie zdefiniuj super w tej klasie i nie Dziedzicz z PrimaryParent.

Myśl o zdefiniowaniu w każdej klasie nie jest dla mnie zbyt atrakcyjna., użycie PrimaryParent pozwala super, oczywiście aliasu opartego na dziedziczeniu, pozostać w linii definicji klasy zamiast ciała klasy, do którego powinny trafić dane. To może być tylko ja.

Oczywiście każda sytuacja jest inna, ale rozważ te rzeczy, które powiedziałem, decydując, którą opcję użyć.

 1
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-24 19:47:05

Nie powiem zbyt wiele oprócz obecnego kodu z komentarzami, które pokazują, że super nie oznacza wywołania bazy!

super != base.

W skrócie, co w ogóle znaczy" super"? a co ma znaczyć "baza"?

  1. super oznacza, wywołanie ostatniego implementatora metody (Nie metody bazowej)
  2. base oznacza wybór, która klasa jest domyślną bazą w dziedziczeniu wielokrotnym.

Te 2 Zasady dotyczą w klasie typedefs.

Rozważmy bibliotekę kto jest super, a kto base?

Aby uzyskać więcej informacji tutaj działa kod do skopiuj wklej do swojego IDE:

#include <iostream>

// Library defiens 4 classes in typical library class hierarchy
class Abstract
{
public:
    virtual void f() = 0;
};

class LibraryBase1 :
    virtual public Abstract
{
public:
    void f() override
    {
        std::cout << "Base1" << std::endl;
    }
};

class LibraryBase2 :
    virtual public Abstract
{
public:
    void f() override
    {
        std::cout << "Base2" << std::endl;
    }
};

class LibraryDerivate :
    public LibraryBase1,
    public LibraryBase2
{
    // base is meaningfull only for this class,
    // this class decides who is my base in multiple inheritance
private:
    using base = LibraryBase1;

protected:
    // this is super! base is not super but base!
    using super = LibraryDerivate;

public:
    void f() override
    {
        std::cout << "I'm super not my Base" << std::endl;
        std::cout << "Calling my *default* base: " << std::endl;
        base::f();
    }
};

// Library user
struct UserBase :
    public LibraryDerivate
{
protected:
    // NOTE: If user overrides f() he must update who is super, in one class before base!
    using super = UserBase; // this typedef is needed only so that most derived version
    // is called, which calls next super in hierarchy.
    // it's not needed here, just saying how to chain "super" calls if needed

    // NOTE: User can't call base, base is a concept private to each class, super is not.
private:
    using base = LibraryDerivate; // example of typedefing base.

};

struct UserDerived :
    public UserBase
{
    // NOTE: to typedef who is super here we would need to specify full name
    // when calling super method, but in this sample is it's not needed.

    // Good super is called, example of good super is last implementor of f()
    // example of bad super is calling base (but which base??)
    void f() override
    {
        super::f();
    }
};

int main()
{
    UserDerived derived;
    // derived calls super implementation because that's what
    // "super" is supposed to mean! super != base
    derived.f();

    // Yes it work with polymorphism!
    Abstract* pUser = new LibraryDerivate;
    pUser->f();

    Abstract* pUserBase = new UserBase;
    pUserBase->f();
}

Kolejnym ważnym punktem jest to:

  1. wywołanie polimorficzne: wywołanie w dół
  2. super call: rozmowy w górę

Wewnątrz main() używamy polimorficznych wywołań w dół, które Super wywołują w górę, niezbyt przydatne w prawdziwym życiu, ale to pokazuje różnicę.

 1
Author: metablaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-05 18:47:19

Używam słowa kluczowego _ _ super. Ale jest specyficzny dla Microsoftu:

Http://msdn.microsoft.com/en-us/library/94dw1w7x.aspx

 0
Author: Mark Ingram,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 09:12:20

Jest to metoda, której używam, która używa makr zamiast typedef. Wiem, że nie jest to sposób działania w C++, ale może to być wygodne podczas łączenia iteratorów ze sobą poprzez dziedziczenie, gdy tylko klasa bazowa najdalej w dół hierarchii działa na odziedziczonym offsecie.

Na przykład:

// 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

Ogólna konfiguracja, której używam, sprawia, że bardzo łatwo jest czytać i kopiować/wklejać między drzewem dziedziczenia, które mają zduplikowany kod, ale muszą być nadpisane, ponieważ Typ powrotu musi pasować do obecnej klasy.

Można by użyć małej litery super do replikacji zachowania widzianego w Javie, ale moim stylem kodowania jest używanie wszystkich wielkich liter w makrach.

 0
Author: Zhro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-15 23:18:08