web-dev-qa-db-fra.com

Quelle est cette étrange syntaxe membre-colon (":") dans le constructeur?

Récemment, j'ai vu un exemple comme celui-ci:

#include <iostream>

class Foo {
public:
  int bar;
  Foo(int num): bar(num) {};
};

int main(void) {
  std::cout << Foo(42).bar << std::endl;
  return 0;
}

Que signifie cette étrange : bar(num)? Cela semble en quelque sorte initialiser la variable membre, mais je n’ai jamais vu cette syntaxe auparavant. Cela ressemble à un appel fonction/constructeur mais pour un int? Cela n'a aucun sens pour moi. Peut-être que quelqu'un pourrait m'éclairer. Et, d'ailleurs, y a-t-il d'autres fonctionnalités du langage ésotérique comme celle-ci, que vous ne trouverez jamais dans un livre C++ ordinaire?

314
nils

C'est une liste d'initialisation des membres . Vous devriez trouver des informations à ce sujet dans n'importe quel bon livre C++ .

Dans la plupart des cas, vous devez initialiser tous les objets membres de la liste d'initialisation des membres (cependant, notez les exceptions répertoriées à la fin de l'entrée FAQ.).

Le point à retenir de l'entrée FAQ est que,

Toutes choses étant égales par ailleurs, votre code fonctionnera plus rapidement si vous utilisez des listes d'initialisation plutôt que d'attribution.

199
James McNellis
Foo(int num): bar(num)    

Cette construction s'appelle liste des membres d'initialisation en C++.

Simplement dit, il initialise votre membre bar à une valeur num.


Quelle est la différence entre l'initialisation et l'assignation dans un constructeur?

Initialisation du membre:

Foo(int num): bar(num) {};

Affectation de membre:

Foo(int num)
{
   bar = num;
}

Il existe une différence significative entre l'initialisation d'un membre à l'aide de la liste d'initialisation du membre et l'attribution d'une valeur à l'intérieur du corps du constructeur.

Lorsque vous initialisez des champs via la liste d'initialisation des membres, les constructeurs sont appelés une fois et l'objet est construit et initialisé en une seule opération. .

Si vous utilisez affectation , les champs seront d'abord initialisés avec les constructeurs par défaut, puis réaffectés (via l'opérateur d'affectation) avec les valeurs réelles. .

Comme vous le voyez, il y a une surcharge supplémentaire de création et d'affectation dans cette dernière, qui peut être considérable pour les classes définies par l'utilisateur.

Cost of Member Initialization = Object Construction 
Cost of Member Assignment = Object Construction + Assignment

Ce dernier est en réalité équivalent à:

Foo(int num) : bar() {bar = num;}

Alors que le premier équivaut à juste:

Foo(int num): bar(num){}

Pour un membre de la classe incorporé (votre exemple de code) ou POD, il n'y a pas de surcharge pratique.


Quand devez-vous utiliser la liste d'initialisation des membres?

Vous aurez aurez (plutôt forcé) utiliser une liste d'initialiseurs de membres si:

  • Votre classe a un membre de référence
  • Votre classe a un membre const non statique ou
  • Votre membre de classe n'a pas de constructeur par défaut ou
  • Pour l'initialisation des membres de la classe de base ou
  • Lorsque le nom du paramètre du constructeur est identique à membre de données (ce n’est pas vraiment un MUST)

Un exemple de code:

class MyClass
{
    public:
        //Reference member, has to be Initialized in Member Initializer List
        int &i;       
        int b;
        //Non static const member, must be Initialized in Member Initializer List
        const int k;  

    //Constructor’s parameter name b is same as class data member 
    //Other way is to use this->b to refer to data member
    MyClass(int a, int b, int c):i(a),b(b),k(c)
    {
         //Without Member Initializer
         //this->b = b;
    }
};

class MyClass2:public MyClass
{
    public:
        int p;
        int q;
        MyClass2(int x,int y,int z,int l,int m):MyClass(x,y,z),p(l),q(m)
        {
        }

};

int main()
{
    int x = 10;
    int y = 20;
    int z = 30;
    MyClass obj(x,y,z);

    int l = 40;
    int m = 50;
    MyClass2 obj2(x,y,z,l,m);

    return 0;
}
  • MyClass2 n'ayant pas de constructeur par défaut, il doit être initialisé via la liste d'initialisation des membres.
  • La classe de base MyClass n'a pas de constructeur par défaut. Par conséquent, pour initialiser son membre, il faut utiliser la liste d'initialisation des membres.

Points importants à noter lors de l’utilisation des listes d’initialiseur de membres:

Les variables de membre de classe sont toujours initialisées dans l'ordre dans lequel elles ont été déclarées dans la classe.

Ils sont non initialisés dans l'ordre dans lequel ils sont spécifiés dans la liste d'initialisation des membres.
En résumé, la liste d’initialisation du membre ne détermine pas l’ordre d’initialisation.

Compte tenu de ce qui précède, il est toujours recommandé de conserver le même ordre de membres pour l'initialisation de membre que l'ordre dans lequel ils sont déclarés dans la définition de classe. En effet, les compilateurs ne préviennent pas si les deux commandes sont différentes, mais un utilisateur relativement nouveau risque de confondre la liste d'initialiseurs des membres avec l'ordre d'initialisation et d'écrire du code dépendant de cette liste.

301
Alok Save

C'est l'initialisation du constructeur. C'est la bonne façon d'initialiser des membres dans un constructeur de classe, car elle empêche l'appel du constructeur par défaut.

Considérez ces deux exemples:

// Example 1
Foo(Bar b)
{
   bar = b;
}

// Example 2
Foo(Bar b)
   : bar(b)
{
}

Dans l'exemple 1:

Bar bar;  // default constructor
bar = b;  // assignment

Dans l'exemple 2:

Bar bar(b) // copy constructor

Tout est question d'efficacité.

16
Josh

Ceci s'appelle une liste d'initialisation. C'est une façon d'initialiser les membres de la classe. Il y a des avantages à utiliser ceci au lieu d'attribuer simplement de nouvelles valeurs aux membres du corps du constructeur, mais si vous avez des membres de la classe qui sont constantes ou références ils - doit être initialisé.

14

Ce n’est pas obscur, c’est le syntaxe de la liste d’initialisation C++

Dans votre cas, x sera initialisé avec _x, y avec _y, z avec _z.

9
wkl

L'autre vous a déjà expliqué que la syntaxe que vous observez s'appelle "liste d'initialisation du constructeur". Cette syntaxe vous permet d’initialiser de manière personnalisée les sous-objets de base et les sous-objets membres de la classe (par opposition à leur permettre d’initialiser par défaut ou de rester non initialisés).

Je veux juste noter que la syntaxe qui, comme vous l'avez dit, "ressemble à un appel de constructeur", n'est pas nécessairement un appel de constructeur. En langage C++, la syntaxe () est juste une forme standard de la syntaxe d'initialisation . Il est interprété différemment pour différents types. Pour les types de classe avec constructeur défini par l'utilisateur, cela signifie une chose (c'est en fait un appel de constructeur), pour les types de classe sans constructeur défini par l'utilisateur, cela signifie autre chose (ce qu'on appelle initialisation de la valeur ) pour ()) vide et pour les types non-classes, cela signifie à nouveau quelque chose de différent (puisque les types non-classes n'ont pas de constructeur ).

Dans votre cas, le membre de données a le type int. int n'est pas un type de classe, il n'a donc pas de constructeur. Pour le type int, cette syntaxe signifie simplement "initialiser bar avec la valeur de num" et c'est tout. C’est fait comme ça, directement, sans aucun constructeur impliqué, puisque, encore une fois, int n’est pas un type de classe, il ne peut donc y avoir de constructeur.

8
AnT

Je ne sais pas comment vous pourriez manquer celui-ci, c'est assez basique. C'est la syntaxe d'initialisation des variables membres ou des constructeurs de classe de base. Cela fonctionne aussi bien pour les anciens types de données que pour les objets de classe.

7
Mark Ransom

Ceci est une liste d'initialisation. Cela initialisera les membres avant que le corps du constructeur ne soit exécuté. Considérer

class Foo {
 public:
   string str;
   Foo(string &p)
   {
      str = p;
   };
 };

contre

class Foo {
public:
  string str;
  Foo(string &p): str(p) {};
};

Dans le premier exemple, str sera initialisé par son constructeur sans argument

string();

devant le corps du constructeur Foo. Dans le constructeur foo, le

string& operator=( const string& s );

sera appelé sur 'str' comme vous le faites str = p;

Dans le second exemple, str sera initialisé directement en appelant son constructeur

string( const string& s );

avec 'p' comme argument.

6
nos

C'est une liste d'initialisation pour le constructeur. Au lieu de construire par défaut x, y et z, puis de leur attribuer les valeurs reçues dans les paramètres, ces membres seront immédiatement initialisés avec ces valeurs. Cela peut ne pas sembler très utile pour floats, mais cela peut vous faire gagner beaucoup de temps avec des classes personnalisées coûteuses à construire.

5
suszterpatt

Vous avez raison, c’est vraiment un moyen d’initialiser les variables membres. Je ne suis pas sûr qu'il y ait beaucoup d'avantages à cela, si ce n'est d'exprimer clairement qu'il s'agit d'une initialisation. Avoir un "bar = num" dans le code pourrait être déplacé, supprimé ou mal interprété beaucoup plus facilement.

5
Aric TenEyck

il y a un autre 'avantage'

si le type de variable membre ne prend pas en charge l'initialisation NULL ou si c'est une référence (qui ne peut pas être initialisée NULL), vous n'avez pas d'autre choix que de fournir une liste d'initialisation.

5
pm100

Pas encore mentionné sur ce fil de discussion: depuis C++ 11, la liste des initialiseurs membres peut utiliser l'initialisation de la liste (aussi appelée "initialisation uniforme", "initialisation masquée"):

Foo(int num): bar{num} {}

qui a la même sémantique que l'initialisation de liste dans d'autres contextes.

1
M.M