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 à partir de la classe de base dans sa version remplacée:
void Derived::foo()
{
super::foo() ;
// ... And then, do something else
}
Il peut même être enchaîné (il me reste encore à en trouver l'usage):
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 l’utilisation de "typedef super" 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, ainsi qu'en C # (où il s'appelle "base", à moins que je ne me trompe). Mais C++ manque ce mot-clé.
Alors, mes questions:
Modifier: Roddy a mentionné le fait que le typedef devrait être privé. Cela signifierait que toute classe dérivée ne serait pas capable de 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 le mot "super", je partage entièrement le point de vue de Roddy: le mot "super" devrait être privé. Je voterais sa réponse deux fois, mais je suppose que je ne peux pas.
Bjarne Stroustrup mentionne dans Conception et évolution du C++ que super
en tant que mot-clé a été examiné par le comité des normes ISO C++ lors de la première normalisation de C++.
Dag Bruck a proposé cette extension, appelant la classe de base "héritée". La proposition mentionnait le problème de l'héritage multiple et aurait signalé des utilisations ambiguës. Même Stroustrup était convaincu.
Après discussion, Dag Bruck (oui, la même personne qui a fait la proposition) a écrit que la proposition était réalisable, techniquement valable, exempte de défauts majeurs et traitait les héritages multiples. Par contre, le rapport qualité-prix était insuffisant et le comité devrait régler un problème plus épineux.
Michael Tiemann est arrivé en retard et a ensuite montré qu'un super typé fonctionnerait parfaitement, en utilisant la même technique que celle décrite dans ce message.
Donc, non, cela ne sera probablement jamais normalisé.
Si vous n'en avez pas, Design and Evolution vaut bien le prix de vente. Les exemplaires utilisés peuvent être obtenus pour environ 10 $.
J'ai toujours utilisé "hérité" plutôt que super. (Probablement en raison d'un arrière-plan Delphi), et je le fais toujours private, afin d'éviter le problème lorsque le paramètre 'inherited' est omis par erreur d'une classe alors 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. J'ai donc peu d'occasions 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 lié à une hiérarchie particulière, et le changer entraînera probablement des problèmes.
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 :: quelque chose compilera fin 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 une très bonne chose, car si vous devez coller une autre couche d'héritage entre Base et Derived, il vous suffit de changer deux choses: 1. la "classe Base: foo" et 2. le typedef
Si je me souviens bien, le comité de normalisation C++ envisageait d'ajouter un mot clé pour cela ... jusqu'à ce que Michael Tiemann indique que cette astuce typedef fonctionne.
En ce qui concerne l'héritage multiple, comme il est sous le contrôle du programmeur, vous pouvez faire ce que vous voulez: peut-être super1 et super2, ou autre chose.
Je viens de trouver une solution de rechange. J'ai un gros problème avec l'approche typedef qui m'a mordue aujourd'hui:
J'ai donc proposé une meilleure solution en utilisant un modèle très simple.
template <class C>
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<Base>
{
// 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 négligente en sélectionnant un nom de type qui devrait alerter les autres développeurs.
Je ne me souviens pas d’avoir vu cela auparavant, mais au premier abord, je l’aime bien. Comme Ferruccio note, cela ne fonctionne pas bien face à MI, mais MI 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 à peu près sûr que je l'ai même vu quelque part dans les bibliothèques de Boost. Cependant, autant que je me souvienne, le nom le plus courant est base
(ou Base
) au lieu de super
.
Cet idiome est particulièrement utile si vous travaillez avec des classes de modèles. A titre d'exemple, considérons la classe suivante (à partir d'un projet réel ):
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;
// …
}
Ne faites pas attention aux noms amusants. Le point important ici est que la chaîne d'héritage utilise des arguments de type pour obtenir un polymorphisme au moment de la compilation. Malheureusement, le niveau d'imbrication de ces modèles devient assez élevé. Par conséquent, les abréviations sont cruciales pour la lisibilité et la maintenabilité.
Je l’ai souvent vu utilisé, parfois comme super_t, lorsque la base est un type de modèle complexe (boost::iterator_adaptor
fait ceci, par exemple)
cette utilisation de typedef est-elle super 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.
Cette utilisation de typedef est-elle super Ok (c’est-à-dire, voyez-vous des raisons fortes ou moins solides de ne pas l’utiliser)?
Cela ne permet pas l'héritage multiple (proprement, en tout cas).
le mot "super" devrait-il être une bonne chose, devrait-il être un peu standardisé en C++, ou est-ce que cette utilisation via un typedef est déjà suffisante?
Pour la raison susmentionnée (héritage multiple), non. La raison pour laquelle vous voyez "super" dans les autres langues que vous avez énumérées est qu’elles ne prennent en charge que l’héritage unique. Il n’ya donc pas de confusion quant à la notion de "super". Certes, dans ces langages, il est IS utile mais il n’a pas vraiment sa place dans le modèle de données C++.
Oh, et FYI: C++/CLI supporte ce concept sous la forme du mot clé "__super". Notez cependant que C++/CLI ne prend pas non plus en charge l'héritage multiple.
Après avoir migré de Turbo Pascal vers C++, je le faisais auparavant pour obtenir 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 autre raison d'utiliser un typedef pour la super-classe est lorsque vous utilisez des modèles complexes dans l'héritage de l'objet.
Par exemple:
template <typename T, size_t C, typename U>
class A
{ ... };
template <typename T>
class B : public A<T,99,T>
{ ... };
En classe B, il serait idéal d’avoir un type pour A, sinon vous seriez obligé de le répéter partout où vous vouliez faire référence aux membres de A.
Dans ces cas, il peut aussi fonctionner avec plusieurs héritages, mais vous n'auriez pas une typedef nommée 'super', cela s'appellerait 'base_A_t' ou quelque chose du genre.
--jeffk ++
J'essayais de résoudre exactement le même problème. J'ai présenté quelques idées, telles que l'utilisation de modèles variadiques et l'extension du pack pour permettre à un nombre arbitraire de parents, mais je me suis rendu compte que cela impliquerait une implémentation telle que 'super0' et 'super1'. Je l'ai jeté parce que ce serait à peine plus utile que de ne pas l'avoir pour commencer.
Ma solution implique une classe d'assistance PrimaryParent
et est implémentée de la manière suivante:
template<typename BaseClass>
class PrimaryParent : virtual public BaseClass
{
protected:
using super = BaseClass;
public:
template<typename ...ArgTypes>
PrimaryParent<BaseClass>(ArgTypes... args) : BaseClass(args...){}
}
Quelle que soit la classe que vous souhaitez utiliser, elle sera déclarée comme telle:
class MyObject : public PrimaryParent<SomeBaseClass>
{
public:
MyObject() : PrimaryParent<SomeBaseClass>(SomeParams) {}
}
Pour éviter d'avoir à utiliser l'héritage virtuel dans PrimaryParent
on BaseClass
, un constructeur utilisant un nombre variable d'arguments est utilisé pour permettre la construction de BaseClass
.
La raison derrière l'héritage public
de BaseClass
dans PrimaryParent
est de laisser MyObject
avoir le contrôle total sur l'héritage de BaseClass
en dépit d'avoir une classe d'assistance entre eux.
Cela signifie que chaque classe que vous voulez avoir super
doit utiliser la classe d'assistance PrimaryParent
, et chaque enfant ne peut hériter que d'une classe en utilisant PrimaryParent
(d'où son nom).
Une autre restriction pour cette méthode, MyObject
ne peut hériter que d'une classe qui hérite de PrimaryParent
, et cette classe doit être héritée avec PrimaryParent
. Voici ce que je veux dire:
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>{}
Avant de rejeter cela comme une option en raison du nombre apparent de restrictions et du fait qu’il existe une classe intermédiaire entre chaque héritage, ces choses ne sont pas mauvaises.
L'héritage multiple est un outil puissant, mais dans la plupart des cas, il n'y aura qu'un seul parent principal, et s'il y a d'autres parents, il s'agira probablement de classes Mixin ou de classes qui n'hériteront pas de PrimaryParent
de toute façon. Si plusieurs héritages sont encore nécessaires (bien que de nombreuses situations gagneraient à utiliser la composition pour définir un objet au lieu d'un héritage), définissez simplement explicitement super
dans cette classe et n'héritez pas de PrimaryParent
.
L'idée de devoir définir super
dans chaque classe ne m'intéresse pas beaucoup, utiliser PrimaryParent
permet à super
, clairement un alias basé sur l'héritage, de rester dans la définition de la classe. ligne au lieu du corps de la classe où les données doivent aller.
Cela pourrait être juste moi cependant.
Bien sûr, chaque situation est différente, mais considérez ce que j'ai dit pour décider quelle option utiliser.
Je ne sais pas si c'est rare ou non, mais j'ai certainement fait la même chose.
Comme cela a été souligné, la difficulté de faire de cette partie du langage elle-même réside dans le fait qu’une classe utilise l’héritage multiple.
Je l'utilise de temps en temps. Juste au moment où je me trouve à taper plusieurs fois le type de classe de base, je le remplace 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, vous pouvez enregistrer les saisies. En outre, les classes de modèle peuvent prendre des arguments qui agissent en tant que règles sur le fonctionnement du modèle. Vous êtes libre de changer le type de base sans avoir à corriger toutes vos références, tant que l'interface de la base reste compatible.
Je pense que l'utilisation à travers le typedef est déjà suffisante. De toute façon, je ne vois pas comment il serait construit dans le langage, car l'héritage multiple signifie qu'il peut y avoir plusieurs classes de base. Vous pouvez donc le saisir comme bon vous semble pour la classe que vous considérez logiquement comme la classe de base la plus importante.
J'utilise le mot clé __super. Mais c'est spécifique à Microsoft:
C’est une méthode que j’utilise qui utilise des macros au lieu d’un typedef. Je sais que ce n'est pas la façon de faire les choses en C++, mais cela peut être pratique pour lier des itérateurs par héritage lorsque seule la classe de base la plus éloignée dans la hiérarchie agit sur un offset hérité.
Par exemple:
// 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
La configuration générique que j'utilise facilite la lecture et le copier/coller entre les arborescences d'héritage contenant du code en double mais qui doivent être remplacées, car le type de retour doit correspondre à la classe actuelle.
On pourrait utiliser un super
minuscule pour reproduire le comportement observé dans Java mais mon style de codage consiste à utiliser toutes les lettres majuscules pour les macros.