Usando “super” in C ++

Il mio stile di codifica include il seguente idioma:

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

Questo mi permette di usare “super” come un alias su Base, ad esempio, nei costruttori:

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

O anche quando si chiama il metodo dalla class base all’interno della sua versione sovrascritta:

 void Derived::foo() { super::foo() ; // ... And then, do something else } 

Può anche essere incatenato (devo ancora trovare l’uso per quello, però):

 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 } 

Ad ogni modo, trovo molto utile l’uso di “typedef super”, ad esempio, quando Base è verboso e / o basato sui modelli.

Il fatto è che super è implementato in Java, così come in C # (dove è chiamato “base”, a meno che non sbaglio). Ma C ++ manca di questa parola chiave.

Quindi, le mie domande:

  • è questo uso di typedef super-comune / raro / mai visto nel codice con cui lavori?
  • è questo l’uso di typedef super Ok (cioè vedi ragioni forti o meno per non usarlo)?
  • dovrebbe “super” essere una buona cosa, dovrebbe essere un po ‘standardizzato in C ++, o è già abbastanza utilizzato da un typedef?

Modifica: Roddy ha menzionato il fatto che il typedef dovrebbe essere privato. Ciò significherebbe che qualsiasi class derivata non sarebbe in grado di usarla senza ridistribuirla. Ma immagino che impedirebbe anche il super-super concatenamento (ma chi piangerà per quello?).

Edit 2: Ora, alcuni mesi dopo aver usato massicciamente “super”, sono pienamente d’accordo con il punto di vista di Roddy: “super” dovrebbe essere privato. Avrei sviato la sua risposta due volte, ma suppongo di non poterlo fare.

Bjarne Stroustrup menziona in Design ed Evolution di C ++ che super come una parola chiave è stata considerata dal comitato degli standard ISO C ++ la prima volta che il C ++ è stato standardizzato.

Dag Bruck ha proposto questa estensione, chiamando la class base “ereditata”. La proposta menzionava il problema dell’ereditarietà multipla e avrebbe avuto usi ambigui segnalati. Persino Stroustrup era convinto.

Dopo la discussione, Dag Bruck (sì, la stessa persona che ha presentato la proposta) ha scritto che la proposta era implementabile, tecnicamente solida e priva di grossi difetti e gestita eredità multiple. D’altra parte, non c’era abbastanza bang per il dollaro, e il comitato dovrebbe gestire un problema spinoso.

Michael Tiemann arrivò tardi, e poi dimostrò che un Super tipico funzionava perfettamente, usando la stessa tecnica richiesta in questo post.

Quindi, no, probabilmente questo non verrà mai standardizzato.

Se non si dispone di una copia, Design and Evolution vale il prezzo di copertina. Si possono avere copie usate per circa $ 10.

Ho sempre usato “ereditato” piuttosto che super. (Probabilmente a causa di uno sfondo di Delphi), e lo rendo sempre privato , per evitare il problema quando l”ereditato’ viene erroneamente omesso da una class, ma una sottoclass prova ad usarlo.

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

Il mio modello di codice standard per la creazione di nuove classi include typedef, quindi ho poche opportunità di ometterlo accidentalmente.

Non penso che il suggerimento incatenato “super :: super” sia una buona idea- Se lo fai, probabilmente sei legato molto a una particolare gerarchia, e cambiarlo probabilmente danneggerà gravemente le cose.

Un problema con questo è che se si dimentica di (ri) definire super per le classi derivate, allora qualsiasi chiamata a super :: qualcosa si compila bene ma probabilmente non chiamerà la funzione desiderata.

Per esempio:

 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() ... } }; 

(Come sottolineato da Martin York nei commenti a questa risposta, questo problema può essere eliminato rendendo il typedef privato piuttosto che pubblico o protetto).

FWIW Microsoft ha aggiunto un’estensione per __super nel proprio compilatore.

Super (o ereditato) è Very Good Thing perché se hai bisogno di attaccare un altro livello di ereditarietà tra Base e Derived, devi solo cambiare due cose: 1. la “class Base: foo” e 2. il typedef

Se ricordo bene, il comitato degli standard C ++ stava pensando di aggiungere una parola chiave per questo … fino a quando Michael Tiemann ha sottolineato che questo trucco typedef funziona.

Per quanto riguarda l’ereditarietà multipla, dal momento che è sotto controllo del programmatore puoi fare tutto ciò che vuoi: forse super1 e super2, o qualsiasi altra cosa.

Ho appena trovato una soluzione alternativa. Ho un grosso problema con l’approccio typedef che mi ha morso oggi:

  • Il typedef richiede una copia esatta del nome della class. Se qualcuno cambia il nome della class ma non cambia il typedef, si avranno dei problemi.

Quindi ho trovato una soluzione migliore usando un modello molto semplice.

 template  struct MakeAlias : C { typedef C BaseAlias; }; 

Quindi ora, invece di

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

hai

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

In questo caso, BaseAlias non è privato e ho cercato di evitare l’uso incauto selezionando un nome di tipo che dovrebbe avvisare gli altri sviluppatori.

Non ricordo di averlo visto prima, ma a prima vista mi piace. Come osserva Ferruccio , non funziona bene di fronte a MI, ma MI è più l’eccezione della regola e non c’è nulla che dice che qualcosa deve essere utilizzabile ovunque per essere utile.

Ho visto questo idioma utilizzato in molti codici e sono abbastanza sicuro di averlo visto da qualche parte nelle librerie di Boost. Tuttavia, per quanto ricordo il nome più comune è base (o Base ) invece di super .

Questo idioma è particolarmente utile se si lavora con classi template. Ad esempio, considera la seguente class (da un progetto reale ):

 template  class Finder >, PizzaChiliFinder> : public Finder >, Default> { typedef Finder >, Default> TBase; // … } 

Non importa i nomi divertenti. Il punto importante qui è che la catena di ereditarietà utilizza gli argomenti di tipo per ottenere il polimorfismo in fase di compilazione. Sfortunatamente, il livello di nidificazione di questi modelli diventa piuttosto elevato. Pertanto, le abbreviazioni sono cruciali per la leggibilità e la manutenibilità.

Ho visto molto spesso che veniva usato, a volte come super_t, quando la base è un tipo di modello complesso ( boost::iterator_adaptor fa, per esempio)

è questo uso di typedef super-comune / raro / mai visto nel codice con cui lavori?

Non ho mai visto questo particolare pattern nel codice C ++ con cui lavoro, ma ciò non significa che non sia fuori là.

è questo l’uso di typedef super Ok (cioè vedi ragioni forti o meno per non usarlo)?

Non consente l’ereditarietà multipla (in modo pulito, comunque).

dovrebbe “super” essere una buona cosa, dovrebbe essere un po ‘standardizzato in C ++, o è già abbastanza utilizzato da un typedef?

Per il motivo sopra citato (ereditarietà multipla), no. Il motivo per cui vedi “super” nelle altre lingue che hai elencato è che supportano solo l’ereditarietà singola, quindi non c’è confusione su cosa “super” si riferisca a. Certo, in quei linguaggi è utile ma in realtà non ha un posto nel modello di dati C ++.

Oh, e FYI: C ++ / CLI supporta questo concetto nella forma della parola chiave “__super”. Si noti, tuttavia, che C ++ / CLI non supporta l’ereditarietà multipla.

Dopo la migrazione da Turbo Pascal a C ++ nel corso della giornata, lo facevo per avere un equivalente per la parola chiave “ereditata” di Turbo Pascal, che funziona allo stesso modo. Tuttavia, dopo aver programmato in C ++ per alcuni anni ho smesso di farlo. Ho scoperto che non ne avevo proprio bisogno.

Un motivo in più per utilizzare un typedef per la superclass è quando si utilizzano modelli complessi nell’eredità dell’object.

Per esempio:

 template  class A { ... }; template  class B : public A { ... }; 

In class B sarebbe ideale avere un typedef per A altrimenti resteresti bloccato ripetendolo ovunque tu volessi fare riferimento ai membri di A.

In questi casi può funzionare anche con ereditarietà multipla, ma non avresti un typedef chiamato ‘super’, sarebbe chiamato ‘base_A_t’ o qualcosa del genere.

–jeffk ++

Non so se sia raro o no, ma certamente ho fatto la stessa cosa.

Come è stato sottolineato, la difficoltà nel rendere questa parte della lingua stessa è quando una class fa uso dell’ereditarietà multipla.

Lo uso di tanto in tanto. Proprio quando mi ritrovo a digitare il tipo di class base un paio di volte, lo sostituirò con un typedef simile al tuo.

Penso che possa essere un buon uso. Come dici tu, se la tua class base è un modello puoi risparmiare la digitazione. Inoltre, le classi template possono utilizzare argomenti che fungono da criteri per il funzionamento del modello. Sei libero di cambiare il tipo di base senza dover sistemare tutti i tuoi riferimenti fino a quando l’interfaccia della base rimane compatibile.

Penso che l’uso attraverso il typedef sia già abbastanza. Non riesco a vedere come sarebbe incorporato nella lingua comunque perché l’ereditarietà multipla significa che ci possono essere molte classi di base, quindi puoi digitarlo come ritieni opportuno per la class che tu ritieni logicamente la più importante class base.

Stavo cercando di risolvere esattamente questo stesso problema; Ho gettato alcune idee, come l’utilizzo di modelli variadici e l’espansione di pacchetti per consentire un numero arbitrario di genitori, ma ho capito che ciò avrebbe comportato un’implementazione come “super0” e “super1”. L’ho buttato via perché sarebbe stato appena più utile di non averlo per cominciare.

La mia soluzione include una class helper PrimaryParent e viene implementata in questo modo:

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

Allora quale class che vorresti usare sarebbe dichiarata come tale:

 class MyObject : public PrimaryParent { public: MyObject() : PrimaryParent(SomeParams) {} } 

Per evitare la necessità di utilizzare l’ereditarietà virtuale in PrimaryParent su BaseClass , viene utilizzato un costruttore che BaseClass un numero variabile di argomenti per consentire la costruzione di BaseClass .

La ragione dietro l’ereditarietà di BaseClass in PrimaryParent è di consentire a MyObject avere il pieno controllo sull’ereditarietà di BaseClass nonostante abbia una class di supporto tra di loro.

Ciò significa che ogni class che si desidera avere super deve utilizzare la class helper di PrimaryParent e ogni figlio può ereditare solo da una class utilizzando PrimaryParent (da cui il nome).

Un’altra restrizione per questo metodo è che MyObject può ereditare solo una class che eredita da PrimaryParent e che deve essere ereditata utilizzando PrimaryParent . Ecco cosa intendo:

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

Prima di scartare questa opzione come opzione a causa del numero apparente di restrizioni e del fatto che esiste una class uomo medio tra ogni eredità, queste cose non sono male.

L’ereditarietà multipla è uno strumento forte, ma nella maggior parte dei casi, ci sarà un solo genitore principale e, se ci sono altri genitori, probabilmente saranno le classi Mixin o le classi che comunque non ereditano da PrimaryParent . Se l’ereditarietà multipla è ancora necessaria (sebbene molte situazioni possano trarre vantaggio dall’uso della composizione per definire un object anziché l’ereditarietà), basta definire in modo esplicito super in quella class e non ereditare da PrimaryParent .

L’idea di dover definire super in ogni class non è molto allettante per me, utilizzando PrimaryParent consente al super , chiaramente un alias basato PrimaryParent , di rimanere nella riga di definizione della class anziché nell’organismo della class in cui i dati dovrebbero andare.

Potrebbe essere solo me.

Naturalmente ogni situazione è diversa, ma considera queste cose che ho detto al momento di decidere quale opzione usare.

Io uso la parola chiave __super. Ma è specifico di Microsoft:

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

Questo è un metodo che utilizzo che utilizza macro invece di typedef. So che questo non è il modo in C ++ di fare le cose, ma può essere conveniente quando si concatenano gli iteratori insieme attraverso l’ereditarietà quando solo la class base più in basso nella gerarchia agisce su un offset ereditato.

Per esempio:

 // some header.h #define CLASS some_iterator #define SUPER_CLASS some_const_iterator #define SUPER static_cast(*this) template class CLASS : SUPER_CLASS { typedef CLASS class_type; class_type& operator++(); }; template typename CLASS::class_type CLASS::operator++( int) { class_type copy = *this; // Macro ++SUPER; // vs // Typedef // super::operator++(); return copy; } #undef CLASS #undef SUPER_CLASS #undef SUPER 

La configurazione generica che utilizzo rende molto semplice la lettura e la copia / incolla tra l’albero di ereditarietà che ha il codice duplicato ma deve essere sovrascritto perché il tipo restituito deve corrispondere alla class corrente.

Si potrebbe usare un super minuscolo per replicare il comportamento visto in Java, ma il mio stile di codifica è quello di utilizzare tutte le lettere maiuscole per le macro.