64 votes

Y a-t-il un constructeur par défaut implicite en C++?

Dans le livre que je lis actuellement (C++ Without Fear), il est dit que si vous ne déclarez pas de constructeur par défaut pour une classe, le compilateur en fournit un pour vous, qui "met à zéro chaque membre de données". J'ai expérimenté cela, et je ne vois aucun comportement de mise à zéro. Je ne trouve également rien à ce sujet sur Google. S'agit-il simplement d'une erreur ou d'une particularité d'un compilateur spécifique?

36 votes

On dirait que vous avez besoin d'un meilleur livre. ;)

13 votes

Oui. Erreur impardonnable, si les "zéros chaque membre de données" est effectivement une citation littérale.

3 votes

Je pense que c'est une idée fausse commune en langage c. J'ai pensé la même chose jusqu'à ce que je visite cette page. Je suis sûr de l'avoir lu quelque part ou appris en cours.

68voto

Loki Astari Points 116129

Si vous ne définissez pas de constructeur, le compilateur définira un constructeur par défaut pour vous.

Construction

L'implémentation de celui-ci

constructeur par défaut est:

  • construire par défaut la classe de base (si la classe de base n'a pas de constructeur par défaut, c'est un échec de compilation)
  • construire par défaut chaque variable membre dans l'ordre de déclaration. (Si un membre n'a pas de constructeur par défaut, c'est un échec de compilation).

Note:
Les données POD (int,float,pointeur, etc.) n'ont pas de constructeur explicite mais l'action par défaut est de ne rien faire (dans la veine de la philosophie C++; nous ne voulons pas payer pour quelque chose à moins que nous ne le demandions explicitement).

Copie

Si aucun destructeur/Constructeur de copie/Opérateur d'affectation de copie n'est défini, le compilateur en construit un pour vous (donc une classe a toujours un destructeur/Constructeur de copie/Opérateur d'affectation (à moins de tricher et de déclarer explicitement l'un d'eux mais de ne pas le définir)).
L'implémentation par défaut est:

Destructeur:

  • Si un destructeur défini par l'utilisateur est défini, exécuter le code fourni.
  • Appeler le destructeur de chaque membre dans l'ordre inverse de la déclaration
  • Appeler le destructeur de la classe de base.

Constructeur de copie:

  • Appeler le Constructeur de copie de classe de base.
  • Appeler le constructeur de copie pour chaque variable membre dans l'ordre de déclaration.

Opérateur d'affectation de copie:

  • Appeler l'opérateur d'affectation de classe de base
  • Appeler l'opérateur d'affectation de copie de chaque variable membre dans l'ordre de déclaration.
  • Retourner une référence à this.

Remarque: La copie de la construction/Opérateur d'affectation de données POD consiste simplement à copier les données (d'où le problème de copie superficielle associé aux pointeurs RAWS).

Déplacement

Si aucun destructeur/Constructeur de copie/Opérateur d'affectation de copie/Constructeur de déplacement/Opérateur d'affectation de déplacement n'est défini, le compilateur construit les opérateurs de déplacement pour vous un de ceux-ci pour vous.
L'implémentation par défaut est:

Constructeur de déplacement déclaré implicitement Si aucun constructeur de déplacement défini par l'utilisateur n'est fourni pour un type de classe (struct, class, ou union), et que tout ce qui suit est vrai:

Constructeur de déplacement:

  • Appeler le Constructeur de copie de classe de base.
  • Appeler le constructeur de déplacement pour chaque variable membre dans l'ordre de déclaration.

Opérateur d'affectation de déplacement:

  • Appeler l'opérateur d'affectation de classe de base
  • Appeler l'opérateur d'affectation de déplacement de chaque variable membre dans l'ordre de déclaration.
  • Retourner une référence à this.

4 votes

À partir de la section 8.5 élément 5 de la norme : "La mise en valeur par défaut d'un objet de type T signifie: ... [si le T est POD] l'objet est initialisé à zéro." et de l'élément 4 de la même section : "Pour initialiser à zéro un objet de type T signifie : si T est un type scalaire, l'objet est mis à la valeur 0 (zéro), considéré comme une expression constante intégrale, convertie en T". Pour POD, si je ne me trompe pas, cela signifie que int i = int(); se termine par i == 0

6 votes

@wilhelmtell : C'est lorsque vous utilisez explicitement l'initialisation par défaut. Dans la situation ci-dessus, les types POD ne sont pas explicitement initialisés par défaut, donc leurs valeurs sont indéfinies. C'est pourquoi j'utilise le terme légèrement plus ambigu de constructeur par défaut.

0 votes

Le compilateur définit-il également un constructeur de déplacement si je n'en définis pas un?

41voto

Bill the Lizard Points 147311

Je pense qu'il vaut la peine de souligner que le constructeur par défaut ne sera créé par le compilateur que si vous ne fournissez aucun constructeur du tout. Cela signifie que si vous ne fournissez qu'un constructeur prenant un argument, le compilateur ne créera pas le constructeur par défaut sans argument pour vous.

Le comportement de mise à zéro dont parle votre livre est probablement spécifique à un compilateur particulier. J'ai toujours supposé que cela peut varier et que vous devriez initialiser explicitement tous les membres de données.

38voto

nobar Points 5849
  • Le compilateur génère-t-il automatiquement un constructeur par défaut?
  • Le constructeur par défaut généré implicitement effectue-t-il une initialisation à zéro?

Si vous analysez de manière légaliste le langage de la norme 2003, alors les réponses sont oui et non. Cependant, ce n'est pas toute l'histoire car contrairement à un constructeur par défaut défini par l'utilisateur, un constructeur par défaut défini implicitement n'est pas toujours utilisé lors de la création d'un objet à partir de zéro -- il existe deux autres scénarios : aucune construction et initialisation des valeurs par membre.

Le cas "aucune construction" est en réalité juste une technicité car il n'est fonctionnellement pas différent que l'appel au constructeur par défaut trivial. L'autre cas est plus intéressant : l'initialisation des valeurs par membre est invoquée en utilisant "()" [comme si on invoquait explicitement un constructeur sans arguments] et cela contourne ce qui est techniquement appelé le constructeur par défaut. À la place, cela effectue de manière récursive une initialisation des valeurs sur chaque membre de données, et pour les types de données primitifs, cela se résout finalement à une initialisation à zéro.

En effet, le compilateur fournit deux différents constructeurs par défaut définis implicitement. Un qui effectue l'initialisation à zéro des données membres primitives et l'autre qui ne le fait pas. Voici quelques exemples de comment vous pouvez invoquer chaque type de constructeur :

    MyClass a; // construction par défaut ou aucune construction
    MyClass b = MyClass(); // initialisation des valeurs par membre

et

    new MyClass; // construction par défaut ou aucune construction
    new MyClass(); // initialisation des valeurs par membre

Remarque : Si un constructeur par défaut déclaré par l'utilisateur existe, alors l'initialisation des valeurs par membre appelle simplement cela et s'arrête.


Voici une analyse quelque peu détaillée de ce que dit la norme à ce sujet...

  • Si vous ne déclarez pas de constructeur, le compilateur crée implicitement un constructeur par défaut [12.1-5]

  • Le constructeur par défaut n'initialise pas les types primitifs [12.1-7]

      MyClass() {} // constructeur implicitement défini
  • Si vous initialisez un objet avec "()", cela n'invoque pas directement le constructeur par défaut. À la place, cela déclenche une longue séquence de règles appelée initialisation des valeurs [8.5-7]

  • L'effet net de l'initialisation des valeurs est que le constructeur par défaut déclaré implicitement n'est jamais appelé. À la place, une initialisation des valeurs par membre récursive est invoquée qui initialise à zéro tout membre primitif et appelle le constructeur par défaut sur tout membre qui a un constructeur déclaré par l'utilisateur [8.5-5]

  • L'initialisation des valeurs s'applique même aux types primitifs -- ils seront initialisées à zéro. [8.5-5]

      int a = int(); // équivalent à int a = 0;

Tout ceci est en réalité peu significatif pour la plupart des cas. L'auteur d'une classe ne peut généralement pas supposer que les membres de données seront remis à zéro lors d'une séquence d'initialisation implicite -- donc toute classe auto-gérée devrait définir son propre constructeur si elle a des membres de données primitifs qui nécessitent une initialisation.

Alors quand est-ce que cela importe?

  • Il peut y avoir des circonstances où du code générique veut forcer l'initialisation de types inconnus. L'initialisation des valeurs fournit un moyen de le faire. Souvenez-vous simplement que l'initialisation implicite à zéro ne se produit pas si l'utilisateur a fourni un constructeur.

  • Par défaut, les données contenues par std::vector sont initialisées par valeur. Cela peut empêcher les débogueurs de mémoire d'identifier les erreurs logiques associées à des tampons mémoire autrement non initialisés.

      vector::resize( size_type sz, T c=T() ); // c par défaut est "initié par valeur"
  • Des tableaux entiers de types primitifs ou de structures de type "plain-old-data" (POD) peuvent être initialisés à zéro en utilisant une syntaxe d'initialisation des valeurs.

      new int[100]();

Ce post a plus de détails sur les variations entre les versions de la norme, et il note également un cas où la norme est appliquée différemment dans les principaux compilateurs.

4 votes

C'est de loin la meilleure réponse - malheureusement, je ne peux voter positivement qu'une seule fois. Donc tout le monde devrait voter aussi !

1 votes

Corrélé -- initialisation de valeurs pour les structures : stackoverflow.com/a/1069634/86967

22voto

tgamblin Points 25755

C++ ne génère pas de constructeur par défaut sauf si vous n'en fournissez pas un vous-même. La norme ne dit rien sur la remise à zéro des membres de données. Par défaut, lorsque vous construisez un objet pour la première fois, ils sont indéfinis.

Cela peut être déroutant car la plupart des types primitifs C++ ont des "constructeurs" par défaut qui les initialisent à zéro (int(), bool(), double(), long(), etc.), mais le compilateur ne les appelle pas pour initialiser les membres POD comme il le fait pour les membres objet.

Il convient de noter que la STL utilise ces constructeurs pour initialiser par défaut le contenu des conteneurs qui contiennent des types primitifs. Vous pouvez consulter cette question pour plus de détails sur la façon dont les éléments dans les conteneurs STL sont initialisés.

11voto

Eclipse Points 27662

Le constructeur par défaut créé pour une classe n'initialisera pas les types intégrés, mais il appellera le constructeur par défaut sur tous les membres définis par l'utilisateur :

class Foo
{
public:
     int x;
     Foo() : x(1) {}
};

class Bar
{
public:
     int y;
     Foo f;
     Foo *fp;
};

int main()
{

    Bar b1; 
    ASSERT(b1.f.x == 1); 
    // Nous ne savons rien sur la valeur de b1.y, ni sur ce que b1.fp contient.

    // L'initialisation des membres de la classe est similaire à l'initialisation normale de la pile.
    int y;  
    Foo f; 
    Foo *fp; 
    ASSERT(f.x == 1);
    // Nous ne savons rien sur la valeur de y, ni sur ce que fp contient.

}

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