Utiliser «super» en C ++


203

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 lors de l'appel de 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é (je dois encore trouver 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 l'utilisation de "typedef super" très utile, par exemple, lorsque Base est verbeux et / ou modélisé.

Le fait est que super est implémenté en Java, ainsi qu'en C # (où il est appelé "base", sauf erreur). Mais C ++ n'a pas ce mot-clé.

Alors, mes questions:

  • cette utilisation de typedef est-elle super courante / rare / jamais vue dans le code avec lequel vous travaillez?
  • cette utilisation de typedef est-elle super ok (c.-à-d. voyez-vous des raisons fortes ou moins fortes de ne pas l'utiliser)?
  • "super" devrait-il être une bonne chose, devrait-il être quelque peu standardisé en C ++, ou est-ce déjà une utilisation via un typedef?

Edit: Roddy a mentionné le fait que le typedef devrait être privé. Cela signifierait qu'aucune classe dérivée ne pourrait l'utiliser sans la redéclarer. Mais je suppose que cela empêcherait également 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 point de vue de Roddy: "super" devrait être privé. Je voterais deux fois sa réponse, mais je suppose que je ne peux pas.


Impressionnant! Exactement ce que je cherchais. Ne pensez pas que j'ai jamais eu besoin d'utiliser cette technique jusqu'à présent. Excellente solution pour mon code multiplateforme.
AlanKley

6
Pour moi ça superressemble Javaet ce n'est rien de mal, mais ... Mais C++supporte l'héritage multiple.
ST3

2
@ user2623967: D'accord. Dans le cas d'un héritage simple, un "super" suffit. Maintenant, si vous avez plusieurs héritages, avoir "superA", "superB", etc. est une bonne solution: vous VOULEZ appeler la méthode à partir d'une implémentation ou d'une autre, vous devez donc DITES quelle implémentation vous voulez. L'utilisation d'un typedef de type "super" vous permet de fournir un nom facilement accessible / inscriptible (au lieu de, disons, écrire MyFirstBase<MyString, MyStruct<MyData, MyValue>>partout)
paercebal

Notez que lorsque vous héritez d'un modèle, vous n'avez pas besoin d'inclure les arguments du modèle lorsque vous y faites référence. Par exemple: template <class baz> struct Foo {...void bar() {...} ...}; struct Foo2: Foo<AnnoyinglyLongListOfArguments> { void bar2() { ... Foo::bar(); ...} };Cela m'a fonctionné avec gcc 9.1 --std = c ++ 1y (c ++ 14).
Thanasis Papoutsidakis

1
... Hum, correction. Il semble fonctionner dans n'importe quelle norme c ++, pas seulement 14.
Thanasis Papoutsidakis

Réponses:


151

Bjarne Stroustrup mentionne dans Design and Evolution of C ++ que supercomme mot-clé a été considéré par le comité des normes ISO C ++ la première fois que C ++ a été standardisé.

Dag Bruck a proposé cette extension, appelant la classe de base "héritée". La proposition mentionnait la question 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 implémentable, techniquement solide et exempte de défauts majeurs, et traitait l'héritage multiple. D'un autre côté, le rapport qualité-prix n'était pas suffisant et le comité devrait gérer un problème plus épineux.

Michael Tiemann est arrivé en retard, puis a montré qu'un super typé fonctionnerait très bien, en utilisant la même technique que celle décrite dans ce post.

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

Si vous n'en avez pas, Design and Evolution vaut bien le prix de couverture. Les copies utilisées peuvent être obtenues pour environ 10 $.


5
D & E est en effet un bon livre. Mais il semble que je devrai le relire - je ne me souviens d'aucune de ces histoires.
Michael Burr

2
Je me souviens de trois caractéristiques qui n'étaient pas acceptées, discutées dans D&E. C'est le premier (recherchez "Michael Tiemann" dans l'index pour trouver l'histoire), la règle des deux semaines est la deuxième (recherchez "la règle des deux semaines" dans l'index), et la troisième a été nommée paramètres (recherchez " arguments nommés "dans l'index).
Max Lybbert

12
Il y a un défaut majeur dans la typedeftechnique: elle ne respecte pas DRY. La seule solution serait d'utiliser des macros laides pour déclarer des classes. Lorsque vous héritez, la base peut être une longue classe de modèles multi-paramètres, ou pire. (par exemple multi classes), vous devrez réécrire tout cela une deuxième fois. Enfin, je vois un gros problème avec les bases de modèles qui ont des arguments de classe de modèles. Dans ce cas, le super est un modèle (et non l'instanciation d'un modèle). Qui ne peut pas être tapé. Même en C ++ 11, vous avez besoin usingde ce cas.
v.oddou

105

J'ai toujours utilisé "hérité" plutôt que super. (Probablement en raison d'un arrière-plan Delphi), et je le rends toujours privé , pour éviter le problème lorsque "hérité" est omis par erreur d'une classe mais qu'une sous-classe essaie de l'utiliser.

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

Mon «modèle de code» standard pour créer de nouvelles classes inclut le typedef, donc j'ai peu d'occasions de l'omettre accidentellement.

Je ne pense pas que la suggestion enchaînée "super :: super" soit une bonne idée. Si vous faites cela, vous êtes probablement très lié à une hiérarchie particulière, et la modifier entraînera probablement un mauvais fonctionnement.


2
Quant au chaînage super :: super, comme je l'ai mentionné dans la question, je dois encore trouver une utilisation intéressante à cela. Pour l'instant, je ne le vois que comme un hack, mais cela valait la peine d'être mentionné, ne serait-ce que pour les différences avec Java (où vous ne pouvez pas chaîner "super").
paercebal

4
Après quelques mois, j'ai été converti à votre point de vue (et j'ai eu un bug à cause d'un "super" oublié, comme vous l'avez mentionné ...). Vous avez tout à fait raison dans votre réponse, y compris le chaînage, je suppose. ^ _ ^ ...
paercebal

comment l'utiliser maintenant pour appeler des méthodes de classe parent?
mLstudent33

cela signifie-t-il que je dois déclarer toutes les méthodes de classe de base virtualcomme indiqué ici: martinbroadhurst.com/typedef-super.html
mLstudent33

36

Un problème avec cela est que si vous oubliez de (re) définir super pour les classes dérivées, alors tout appel à super :: quelque chose se compilera correctement mais n'appellera probablement pas la fonction souhaité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é.)


Merci pour la remarque. Cet effet secondaire avait échappé à mon attention. Bien qu'il ne compilera probablement pas pour l'usage du constructeur, partout ailleurs, je suppose que le bogue serait là.
paercebal

5
Cependant, typedef privé empêchera l'utilisation en chaîne, mentionnée dans le message d'origine.
AnT

1
Rencontrer ce bug exact m'a conduit à cette question :(
Steve Vermeulen

donc utiliser super1pour Base et super2dans Derived? Le DerivedAgain peut utiliser les deux?
mLstudent33

20

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


Quelques développeurs ici ont commencé à pousser pour utiliser __super. Au début, j'ai repoussé car je sentais que c'était "faux" et "non standard". CEPENDANT, j'ai appris à l'aimer.
Aardvark

8
Je travaille sur une application Windows et j'adore l'extension __super. Cela m'attriste que le comité des normes l'a rejeté en faveur de l'astuce typedef mentionnée ici, car bien que cette astuce typedef soit bonne, elle nécessite plus de maintenance qu'un mot clé de compilation lorsque vous changez la hiérarchie d'héritage et gère correctement l'héritage multiple (sans nécessiter deux typedefs comme super1 et super2). En bref, je suis d'accord avec l'autre commentateur que l'extension MS est TRÈS utile, et quiconque utilisant exclusivement Visual Studio devrait fortement envisager de l'utiliser.
Brian

15

Super (ou hérité) est une très bonne chose parce que si vous devez coller une autre couche d'héritage entre Base et Derived, vous n'avez qu'à changer deux choses: 1. la "classe Base: foo" et 2. le typedef

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

En ce qui concerne l'héritage multiple, car il est sous le contrôle du programmeur, vous pouvez faire tout ce que vous voulez: peut-être super1 et super2, ou autre chose.


13

Je viens de trouver une solution alternative. 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 la typedef, vous rencontrerez des problèmes.

J'ai donc trouvé 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;
};

vous avez

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

Dans ce cas, BaseAliasn'est pas privé et j'ai essayé de me prémunir contre une utilisation imprudente en sélectionnant un nom de type qui devrait alerter les autres développeurs.


4
publicalias est un inconvénient, car vous êtes ouvert au bug mentionné dans les réponses de Roddy et Kristopher (par exemple, vous pouvez (par erreur) dériver à la Derivedplace de MakeAlias<Derived>)
Alexander Malakhov

3
Vous n'avez pas non plus accès aux constructeurs de classe de base dans vos listes d'initialisation. (Cela peut être compensé en C ++ 11 en utilisant des constructeurs hérités MakeAliasou une transmission parfaite, mais cela vous oblige à vous référer aux MakeAlias<BaseAlias>constructeurs plutôt qu'à vous référer Cdirectement aux constructeurs de.)
Adam H. Peterson

12

Je ne me souviens pas avoir vu cela auparavant, mais à première vue, j'aime ça. 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.


6
Votez juste pour la phrase «il n'y a rien qui dit que quelque chose doit être utilisable partout pour être utile».
Tanktalus

1
D'accord. C'est utile. Je regardais simplement la bibliothèque de traits de type boost pour voir s'il y avait un moyen de générer le typedef via un modèle. Malheureusement, cela ne semble pas possible.
Ferruccio

9

J'ai vu cet idiome utilisé dans de nombreux codes et je suis presque sûr de l'avoir même vu quelque part dans les bibliothèques de Boost. Cependant, pour autant que je m'en 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. À titre d'exemple, considérons la classe suivante (à partir d'un vrai projet ):

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 vous occupez pas des noms drôles. 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é.


J'avais récemment utilisé le "super" pour la même raison. L'héritage public était trop pénible à gérer sinon ... :-) ...
paercebal


4

cette utilisation de typedef est-elle super courante / 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 signifie pas qu'il n'est pas là.

cette utilisation de typedef est-elle super ok (c.-à-d. voyez-vous des raisons fortes ou moins fortes de ne pas l'utiliser)?

Il ne permet pas l'héritage multiple (proprement, de toute façon).

"super" devrait-il être une bonne chose, devrait-il être quelque peu standardisé en C ++, ou est-ce déjà une utilisation via un typedef?

Pour la raison citée ci-dessus (héritage multiple), non. La raison pour laquelle vous voyez "super" dans les autres langues que vous avez répertoriées est qu'elles ne prennent en charge que l'héritage unique, donc il n'y a aucune confusion quant à ce à quoi "super" fait référence. Certes, dans ces langages, il est utile, mais il n'a pas vraiment sa place dans le modèle de données C ++.

Oh, et pour info: C ++ / CLI prend en charge ce concept sous la forme du mot clé "__super". Veuillez noter, cependant, que C ++ / CLI ne prend pas en charge l'héritage multiple non plus.


4
En contrepoint, Perl a à la fois un héritage multiple et SUPER. Il y a une certaine confusion, mais l'algorithme que la machine virtuelle utilise pour trouver quelque chose est clairement documenté. Cela dit, je vois rarement MI utilisé lorsque plusieurs classes de base offrent les mêmes méthodes où une confusion peut survenir de toute façon.
Tanktalus

1
Microsoft Visual Studio implémente le mot clé __super pour C ++, que ce soit pour CLI ou non. Si une seule des classes héritées propose une méthode de signature correcte (le cas le plus courant, comme mentionné par Tanktalus), elle choisit simplement le seul choix valide. Si deux classes héritées ou plus fournissent une correspondance de fonction, cela ne fonctionne pas et vous oblige à être explicite.
Brian

3

Une raison supplémentaire d'utiliser un typedef pour la superclasse 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>
{ ... };

Dans la classe B, il serait idéal d'avoir un typedef pour A, sinon vous seriez obligé de le répéter partout où vous voulez référencer les membres de A.

Dans ces cas, il peut également fonctionner avec plusieurs héritages, mais vous n'auriez pas de typedef nommé 'super', il serait appelé 'base_A_t' ou quelque chose comme ça.

--jeffk ++


2

Après avoir migré de Turbo Pascal vers C ++ dans la journée, je faisais cela 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.


1

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 faire cette partie du langage lui-même est quand une classe utilise l'héritage multiple.


1

J'utilise cela de temps en temps. Juste au moment où je me retrouve à taper le type de classe de base plusieurs fois, je le remplacerai par un typedef similaire au vôtre.

Je pense que cela peut être une bonne utilisation. Comme vous le dites, si votre classe de base est un modèle, elle peut économiser la saisie. De plus, les classes de modèles peuvent prendre des arguments qui agissent comme des politiques sur la façon dont le modèle doit fonctionner. Vous êtes libre de changer le type de base sans avoir à y corriger toutes vos références tant que l'interface de la base reste compatible.

Je pense que l'utilisation via le typedef est déjà suffisante. Je ne vois pas comment il serait intégré au langage de toute façon, car l'héritage multiple signifie qu'il peut y avoir de nombreuses classes de base, vous pouvez donc le taper comme bon vous semble pour la classe que vous pensez logiquement être la classe de base la plus importante.


1

J'essayais de résoudre exactement ce même problème; J'ai lancé quelques idées, telles que l'utilisation de modèles variadiques et l'expansion du pack pour permettre un nombre arbitraire de parents, mais j'ai réalisé que cela entraînerait une implémentation comme 'super0' et 'super1'. Je l'ai mis à la poubelle parce que ce serait à peine plus utile que de ne pas l'avoir au départ.

Ma solution implique une classe d'assistance PrimaryParentet est implémentée comme suit:

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

La classe que vous souhaitez utiliser serait alors 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 PrimaryParenton BaseClass, un constructeur prenant un nombre variable d'arguments est utilisé pour permettre la construction de BaseClass.

La raison derrière l' publichéritage de BaseClassinto PrimaryParentest de laisser MyObjectavoir un contrôle total sur l'héritage deBaseClass malgré une classe d'assistance entre eux.

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

Une autre restriction pour cette méthode est qu'elle MyObjectne peut hériter que d'une classe qui hérite de PrimaryParentcelle-ci et que celle-ci doit être héritée en utilisant 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 en 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, ce seront probablement des classes Mixin ou des classes qui n'héritent pas de PrimaryParenttoute façon. Si l'héritage multiple est toujours nécessaire (bien que de nombreuses situations gagneraient à utiliser la composition pour définir un objet au lieu de l'héritage), définissez simplement explicitement superdans cette classe et n'héritez pas dePrimaryParent .

L'idée de devoir définir superdans chaque classe n'est pas très attrayante pour moi, en utilisant PrimaryParentpermet super, clairement un alias basé sur l'héritage, de rester dans la ligne de définition de classe au lieu du corps de classe où les données devraient aller.

Mais c'est peut-être juste moi.

Bien sûr, chaque situation est différente, mais tenez compte de ces choses que j'ai dites lors du choix de l'option à utiliser.


1

Je ne dirai pas grand-chose sauf le code actuel avec des commentaires qui démontre que super ne signifie pas appeler la base!

super != base.

En bref, qu'est-ce que "super" est censé signifier de toute façon? et alors que signifie "base"?

  1. super signifie, appeler le dernier implémenteur d'une méthode (pas la méthode de base)
  2. base signifie, choisir quelle classe est la base par défaut dans l'héritage multiple.

Ces 2 règles s'appliquent aux typedefs en classe.

Considérez l'implémentateur de la bibliothèque et l'utilisateur de la bibliothèque, qui est super et qui est la base?

pour plus d'informations, voici le code de travail pour copier-coller dans votre 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();
}

Un autre point important ici est le suivant:

  1. appel polymorphe: appels vers le bas
  2. super appel: appels vers le haut

à l'intérieur, main()nous utilisons des appels d'appel polymorphes qui super appellent vers le haut, pas vraiment utiles dans la vie réelle, mais cela démontre la différence.



0

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 lors de l'enchaînement d'itérateurs par héritage lorsque seule la classe de base la plus en aval 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<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 l'arborescence d'héritage qui a du code en double mais doit être remplacée car le type de retour doit correspondre à la classe actuelle.

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.