Utiliser “super” en C ++

Mon style de codage comprend l’idiome suivant:

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

Cela me permet d’utiliser “super” comme alias de Base, par exemple dans les constructeurs:

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

Ou même lorsque vous appelez la méthode depuis la classe de base dans sa version surchargée:

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

Il peut même être enchaîné (je n’ai toujours pas trouvé l’utilisation pour cela):

 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 } 

Quoi qu’il en soit, je trouve que l’utilisation de “typedef super” est très utile, par exemple, lorsque Base est verbeux et / ou basé sur un modèle.

Le fait est que super est implémenté en Java, aussi bien qu’en C # (où il s’appelle “base”, sauf si je me trompe). Mais C ++ manque de ce mot clé.

Donc, mes questions:

  • est-ce que cette utilisation de typedef est très commune / rare / jamais vue dans le code avec lequel vous travaillez?
  • est-ce que cette utilisation de typedef est super Ok (c.-à-d. voyez-vous des raisons fortes ou pas si fortes de ne pas l’utiliser)?
  • “super” devrait-il être une bonne chose, devrait-il être quelque peu normalisé en C ++, ou est-ce que cette utilisation est déjà suffisante avec un typedef?

Edit: Roddy a mentionné le fait que le typedef devrait être privé. Cela signifierait que toute classe dérivée ne pourrait pas l’utiliser sans la redéclarer. Mais je suppose que cela empêcherait aussi le super :: super chaînage (mais qui va pleurer pour ça?).

Edit 2: Maintenant, quelques mois après avoir utilisé massivement “super”, je suis entièrement d’accord avec le sharepoint vue de Roddy: “super” devrait être privé. Je prendrais sa réponse deux fois, mais je suppose que je ne peux pas.

Bjarne Stroustrup mentionne dans Design and Evolution de C ++ que super tant que mot clé a été considéré par le comité des normes ISO C ++ la première fois que le C ++ a été normalisé.

Dag Bruck a proposé cette extension, appelant la classe de base “héritée”. La proposition mentionnait la question de l’inheritance multiple et aurait signalé des utilisations ambiguës. Même Stroustrup était convaincu.

Après discussion, Dag Bruck (oui, la même personne qui fait la proposition) a écrit que la proposition était réalisable, techniquement valable et exempte de défauts majeurs, et qu’elle traitait de multiples inheritances. D’un autre côté, il n’y avait pas assez d’argent pour le faire, et le comité devrait régler un problème épineux.

Michael Tiemann est arrivé en retard et a ensuite montré qu’un super typé fonctionnerait très bien, en utilisant la même technique que celle utilisée dans ce post.

Donc, non, cela ne sera probablement jamais normalisé.

Si vous n’avez pas de copie, Design and Evolution vaut bien le prix de vente. Les copies utilisées peuvent être obtenues pour environ 10 dollars.

J’ai toujours utilisé “hérité” plutôt que super. (Probablement dû à un arrière-plan Delphi), et je le rend toujours privé , pour éviter le problème lorsque le mot «inherited» est omis par erreur dans une classe mais qu’une sous-classe tente de l’utiliser.

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

Mon «modèle de code» standard pour la création de nouvelles classes inclut le typedef, donc j’ai peu de chance de l’omettre accidentellement.

Je ne pense pas que la suggestion “super :: super” enchaînée soit une bonne idée. Si vous le faites, vous êtes probablement très impliqué dans une hiérarchie particulière, et changer cela risque de casser les choses.

Un problème avec ceci est que si vous oubliez de (re) définir super pour les classes dérivées, alors tout appel à super :: quel comstack bien mais n’appellera probablement pas la fonction désirée.

Par exemple:

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

(Comme l’a souligné Martin York dans les commentaires de cette réponse, ce problème peut être éliminé en rendant le typedef privé plutôt que public ou protégé.)

FWIW Microsoft a ajouté une extension pour __super dans son compilateur.

Super (ou hérité) est très bon parce que si vous avez besoin de coller un autre calque d’inheritance entre Base et Derived, vous n’avez qu’à changer deux choses: 1. le “class Base: foo” et 2. le typedef

Si je me souviens bien, le comité des normes C ++ envisageait d’append un mot-clé pour cela… jusqu’à ce que Michael Tiemann indique que cette astuce typedef fonctionne.

En ce qui concerne l’inheritance multiple, comme il est sous le contrôle du programmeur, vous pouvez faire ce que vous voulez: peut-être super1 et super2, ou peu importe.

Je viens de trouver une solution de rechange. J’ai un gros problème avec l’approche typedef qui m’a mordu aujourd’hui:

  • Le typedef nécessite une copie exacte du nom de la classe. Si quelqu’un change le nom de la classe mais ne change pas le typedef, vous rencontrerez des problèmes.

J’ai donc trouvé une meilleure solution en utilisant un modèle très simple.

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

Alors maintenant, au lieu de

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

tu as

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

Dans ce cas, BaseAlias n’est pas privé et j’ai essayé de me protéger contre une utilisation imprudente en sélectionnant un nom de type qui devrait alerter les autres développeurs.

Je ne me souviens pas de l’avoir vu auparavant, mais à première vue je l’aime bien. Comme le note Ferruccio , cela ne fonctionne pas bien face à l’IM, mais l’IM est plus l’exception que la règle et rien ne dit que quelque chose doit être utilisable partout pour être utile.

J’ai vu cet idiome employé dans de nombreux codes et je suis presque certain de l’avoir vu quelque part dans les bibliothèques de Boost. Cependant, pour autant que je me souvienne, le nom le plus commun est base (ou Base ) au lieu de super .

Cet idiome est particulièrement utile si vous travaillez avec des classes de modèles. À titre d’exemple, considérons la classe suivante (issue d’un projet réel ):

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

Ne faites pas attention aux noms amusants. Le point important ici est que la chaîne d’inheritance utilise des arguments de type pour obtenir un polymorphism à la compilation. Malheureusement, le niveau d’imbrication de ces modèles est assez élevé. Par conséquent, les abréviations sont cruciales pour la lisibilité et la maintenabilité.

Je l’ai assez souvent vu utilisé, parfois comme super_t, quand la base est un type de template complexe ( boost::iterator_adaptor fait ça, par exemple)

est-ce que cette utilisation de typedef est très commune / rare / jamais vue dans le code avec lequel vous travaillez?

Je n’ai jamais vu ce modèle particulier dans le code C ++ avec lequel je travaille, mais cela ne veut pas dire que ce n’est pas le cas.

est-ce que cette utilisation de typedef est super Ok (c.-à-d. voyez-vous des raisons fortes ou pas si fortes de ne pas l’utiliser)?

Il ne permet pas l’inheritance multiple (proprement, de toute façon).

“super” devrait-il être une bonne chose, devrait-il être quelque peu normalisé en C ++, ou est-ce que cette utilisation est déjà suffisante avec un typedef?

Pour la raison citée ci-dessus (inheritance multiple), non. La raison pour laquelle vous voyez “super” dans les autres langages que vous avez listés est qu’ils ne supportent que l’inheritance simple, donc il n’y a pas de confusion sur ce à quoi “super” fait référence. Certes, dans ces langues, il est utile, mais il n’a pas vraiment sa place dans le modèle de données C ++.

Oh, et FYI: C ++ / CLI prend en charge ce concept sous la forme du mot clé “__super”. Notez cependant que C ++ / CLI ne supporte pas non plus l’inheritance multiple.

Après avoir migré de Turbo Pascal vers C ++ dans le passé, je le faisais pour avoir un équivalent pour le mot-clé “hérité” de Turbo Pascal, qui fonctionne de la même manière. Cependant, après avoir programmé en C ++ pendant quelques années, j’ai arrêté de le faire. J’ai trouvé que je n’en avais pas vraiment besoin.

Une raison supplémentaire d’utiliser un typedef pour la superclasse est lorsque vous utilisez des modèles complexes dans l’inheritance de l’object.

Par exemple:

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

Dans la classe B, l’idéal serait d’avoir un typedef pour A sinon vous seriez coincé en le répétant partout où vous voulez référencer les membres de A.

Dans ces cas-là, il peut fonctionner avec plusieurs inheritances, mais vous n’auriez pas un typedef nommé «super», il s’appellerait «base_A_t» ou quelque chose comme ça.

–jeffk ++

Je ne sais pas si c’est rare ou pas, mais j’ai certainement fait la même chose.

Comme cela a été souligné, la difficulté de rendre cette partie du langage elle-même réside dans le fait qu’une classe utilise l’inheritance multiple.

Je l’utilise de temps en temps. Juste au moment où je me trouve à taper le type de classe de base à quelques resockets, je le remplacerai par un typedef similaire au vôtre.

Je pense que cela peut être un bon usage. Comme vous le dites, si votre classe de base est un modèle, elle peut sauver la saisie. En outre, les classes de modèle peuvent prendre des arguments qui agissent en tant que stratégies pour le fonctionnement du modèle. Vous êtes libre de changer le type de base sans avoir à y reporter toutes vos références, tant que l’interface de la base rest compatible.

Je pense que l’utilisation par le typedef est déjà suffisante. Je ne vois pas comment il serait intégré au langage, car l’inheritance multiple signifie qu’il peut y avoir beaucoup de classes de base. Vous pouvez donc le taper comme vous le souhaitez pour la classe qui, selon vous, est la classe de base la plus importante.

J’essayais de résoudre exactement le même problème. J’ai évoqué quelques idées, comme l’utilisation de modèles variadiques et l’expansion de packs pour permettre un nombre arbitraire de parents, mais j’ai réalisé que cela se traduirait par une implémentation telle que «super0» et «super1». Je l’ai détruit parce que ce serait à peine plus utile que de ne pas l’avoir au départ.

Ma solution implique une classe auxiliaire PrimaryParent et est implémentée comme PrimaryParent :

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

Alors quelle classe que vous voulez utiliser sera déclarée comme telle:

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

Pour éviter d’avoir à utiliser l’inheritance virtuel dans PrimaryParent sur BaseClass , un constructeur prenant un nombre variable d’arguments est utilisé pour permettre la construction de BaseClass .

La raison derrière l’inheritance public de BaseClass dans PrimaryParent est de laisser MyObject avoir le contrôle total sur l’inheritance de BaseClass dépit d’une classe d’assistance entre eux.

Cela signifie que chaque classe que vous souhaitez utiliser doit utiliser la classe d’assistance PrimaryParent et que chaque enfant ne peut hériter que d’une classe utilisant PrimaryParent (d’où le nom).

Une autre ressortingction pour cette méthode est que MyObject ne peut hériter que d’une classe héritant de PrimaryParent et que celle-ci doit être héritée en utilisant PrimaryParent . Voici ce que je veux dire:

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

Avant de supprimer cette option en raison du nombre apparent de ressortingctions et du fait qu’il existe une classe intermédiaire entre chaque inheritance, ces choses ne sont pas mauvaises.

L’inheritance multiple est un outil puissant, mais dans la plupart des cas, il n’y aura qu’un seul parent et s’il y a d’autres parents, ce seront probablement des classes Mixin ou des classes qui n’héritent pas de PrimaryParent . Si l’inheritance multiple est toujours nécessaire (bien que de nombreuses situations auraient intérêt à utiliser la composition pour définir un object à la place de l’inheritance), définissez explicitement super dans cette classe et n’héritez pas de PrimaryParent .

L’idée d’avoir à définir super dans chaque classe ne m’intéresse pas beaucoup, l’utilisation de PrimaryParent permet à super , clairement un alias basé sur l’inheritance, de restr dans la ligne de définition de classe au lieu du corps de classe où les données doivent aller.

Cela pourrait être moi si.

Bien sûr, chaque situation est différente, mais considérez ces choses comme je l’ai dit en décidant quelle option utiliser.

J’utilise le mot clé __super. Mais c’est spécifique à Microsoft:

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

C’est une méthode que j’utilise et qui utilise des macros au lieu d’un typedef. Je sais que ce n’est pas la manière C ++ de faire les choses, mais cela peut être pratique lorsque vous enchaînez des iterators ensemble par inheritance lorsque seule la classe de base la plus éloignée de la hiérarchie agit sur un décalage hérité.

Par exemple:

 // 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 configuration générique que j’utilise rend très facile la lecture et la copie / collage entre les arbres d’inheritance qui ont un code en double mais qui doivent être remplacés car le type de retour doit correspondre à la classe en cours.

On pourrait utiliser un minuscule super pour reproduire le comportement vu en Java, mais mon style de codage consiste à utiliser toutes les lettres majuscules pour les macros.