10 votes

En C++, existe-t-il un consensus concernant le comportement par défaut par rapport au code explicite ?

Je me demande s'il y a une raison d'écrire explicitement du code qui fait la même chose que le comportement par défaut du C++.

Voici un peu de code :

class BaseClass
{
public:
    virtual ~BaseClass() {}

    virtual void f() { /* do something */ }
};

class ExplicitClass
    : public BaseClass
{
public:
    ExplicitClass()
        : BaseClass()   // <-- explicit call of base class constructor
    {
        // empty function
    }

    virtual ~ExplicitClass() {}  // <-- explicit empty virtual destructor

    virtual void f() { BaseClass::f(); }  // <-- function just calls base
};

class ImplicitClass
    : public BaseClass
{    
};

Je suis surtout curieux dans le domaine de la refactorisation et de l'évolution de la base de code. Je ne pense pas que beaucoup de codeurs aient l'intention d'écrire du code comme ça, mais ça peut finir par ressembler à ça quand le code change au fil du temps.

Y a-t-il un intérêt à laisser le code présent dans le fichier ExplicitClass ? Je peux voir l'avantage que cela vous montre ce qui se passe, mais cela semble être un peu long et risqué.

Personnellement, je préfère supprimer tout code qui est un code de comportement par défaut (tel que ImplicitClass ).

Y a-t-il un consensus en faveur de l'une ou l'autre solution ?

1voto

PiotrNycz Points 8145

Il existe deux approches de cette question :

  1. Définissez tout, même si le compilateur génère la même chose,
  2. Ne définit rien que compile fera mieux.

Les croyants de (1) utilisent des règles comme : "Toujours définir le c-tor par défaut, le c-tor par copie, l'opérateur d'affectation et le d-tor".

(1) Les croyants pensent qu'il est plus sûr d'avoir plus que de manquer quelque chose. Malheureusement, la règle (1) est particulièrement appréciée par nos managers - ils pensent qu'il vaut mieux avoir que ne pas avoir. Des règles comme "toujours définir les quatre grands" font partie de la "norme de codage" et doivent être suivies.

Je crois en (2). Et pour les entreprises où de telles normes de codage existent, je mets toujours le commentaire "Ne pas définir de copie c-tor car le compilateur le fait mieux".

0voto

Zdeslav Vojkovic Points 8911

Comme la question porte sur le consensus, je ne peux pas répondre, mais je trouve le commentaire d'ildjarn amusant et correct.

Pour ce qui est de votre question de savoir s'il y a une raison de l'écrire comme cela, il n'y en a pas car les classes explicites et implicites se comportent de la même manière. Les gens le font parfois pour des raisons de "maintenance", par exemple, si la classe dérivée f est implémenté d'une manière différente pour ne pas oublier d'appeler la classe de base. Personnellement, je ne trouve pas cela utile.

0voto

qdii Points 3927

L'une ou l'autre de ces méthodes convient, à condition que vous compreniez ce qui se passe réellement et les problèmes qui peuvent survenir si vous n'écrivez pas vous-même les fonctions.

EXCEPTION-SÉCURITÉ :

Le compilateur générera les fonctions en ajoutant implicitement les éléments nécessaires à l'exécution de la fonction. throw conditions. Pour un constructeur créé implicitement, ce serait toutes les conditions de lancement des classes de base et des membres.

CODE MAL FORMÉ

Il y a des cas délicats où certaines des fonctions membres générées automatiquement seront mal formées. Voici un exemple :

class Derived;

class Base
{
public:
  virtual Base& /* or Derived& */
  operator=( const Derived& ) throw( B1 );

  virtual ~Base() throw( B2 );
};

class Member
{
public:
  Member& operator=( const Member& ) throw( M1 );
  ~Member() throw( M2 );
};

class Derived : public Base
{
  Member m_;
  //   Derived& Derived::operator=( const Derived& )
  //            throw( B1, M1 ); // error, ill-formed
  //   Derived::~Derived()
  //            throw( B2, M2 ); // error, ill-formed
};

Le site operator= est mal formé car sa directive de lancement devrait être au moins aussi restrictive que sa classe de base, ce qui signifie qu'il devrait lancer soit B1, soit rien du tout. Ceci est logique car un objet dérivé peut aussi être vu comme un objet de base.

Notez qu'il est parfaitement légal d'avoir une fonction mal formée tant que vous ne l'invoquez jamais.

Je suis en train de réécrire le GotW #69 ici, donc si vous voulez plus de détails, vous pouvez les trouver ici

0voto

justin Points 72871

Cela dépend de la façon dont vous aimez structurer et lire les programmes. bien sûr, il y a des préférences et des raisons pour et contre chacune.

class ExplicitClass
    : public BaseClass
{
public:

l'initialisation est très importante. ne pas initialiser une base ou un membre peut produire des avertissements, à juste titre ou en attrapant un bogue dans certains cas. donc cela commence vraiment à avoir du sens si cette collection d'avertissements est activée, vous gardez les niveaux d'avertissement très élevés, et les comptes d'avertissement diminuent. cela aide aussi à démontrer l'intention :

    ExplicitClass()
        : BaseClass()   // <-- explicit call of base class constructor
    {
        // empty function
    }

un destructeur virtuel vide est, IME, statistiquement le meilleur endroit pour exporter une virtual (bien sûr, cette définition se trouverait ailleurs si elle était visible par plus d'une traduction). vous voulez que cela soit exporté parce qu'il y a une tonne d'informations sur les rtti et les vtable qui pourraient se retrouver sous la forme d'une masse inutile dans votre binaire. en fait, je définis très régulièrement des destructeurs vides pour cette raison :

    virtual ~ExplicitClass() {}  // <-- explicit empty virtual destructor

Il peut s'agir d'une convention au sein de votre groupe ou d'un document indiquant que c'est exactement ce que l'implémentation est censée faire. Cela peut également être utile (subjectif) dans les grandes bases de code ou au sein de hiérarchies complexes, car cela peut également vous aider à vous rappeler de l'interface dynamique que le type est censé adopter. certaines personnes préfèrent toutes les déclarations dans la sous-classe parce qu'elles peuvent voir toute l'implémentation dynamique de la classe en un seul endroit. ainsi la localité les aide dans le cas où la hiérarchie/interface de la classe est plus grande que la pile mentale du programmeur. comme le destructeur, ce virtuel peut aussi être un bon endroit pour exporter l'information sur le type :

    virtual void f() { BaseClass::f(); }  // <-- function just calls base
};

Bien sûr, il devient difficile de suivre un programme ou un raisonnement si vous ne définissez que si qualifié. Ainsi, certaines bases de code peuvent être plus faciles à suivre si vous vous en tenez aux conventions parce que c'est plus clair que de documenter pourquoi un destructeur vide est exporté à chaque fois.

une dernière raison (qui va dans les deux sens) est que les définitions explicites par défaut peuvent augmenter et diminuer les temps de construction et de liaison.

heureusement, il est désormais plus facile et sans ambiguïté de spécifier des méthodes et des constructeurs par défaut et supprimés.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X