Pourquoi devrais-je préférer utiliser la liste d'initialisation des membres?

Réponses:

278

Pour les membres de la classe POD , cela ne fait aucune différence, c'est juste une question de style. Pour les membres de classe qui sont des classes, cela évite un appel inutile à un constructeur par défaut. Considérer:

class A
{
public:
    A() { x = 0; }
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B()
    {
        a.x = 3;
    }
private:
    A a;
};

Dans ce cas, le constructeur de for Bappellera le constructeur par défaut de A, puis initialisera a.xà 3. Une meilleure façon serait que Ble constructeur de A's appelle directement le constructeur de' dans la liste d'initialisation:

B()
  : a(3)
{
}

Cela n'appellerait que Ale A(int)constructeur et non son constructeur par défaut. Dans cet exemple, la différence est négligeable, mais imaginez si vous voulez que Ale constructeur par défaut en fasse plus, comme l'allocation de mémoire ou l'ouverture de fichiers. Vous ne voudriez pas faire cela inutilement.

De plus, si une classe n'a pas de constructeur par défaut, ou si vous avez une constvariable membre, vous devez utiliser une liste d'initialisation:

class A
{
public:
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B() : a(3), y(2)  // 'a' and 'y' MUST be initialized in an initializer list;
    {                 // it is an error not to do so
    }
private:
    A a;
    const int y;
};
Adam Rosenfield
la source
5
un must est aussi pour le cas important d'une référence
4pie0
5
Pourquoi ne pas utiliser "a (3);" ou "a = A (3);" dans le corps du constructeur par défaut de B?
Sergey
1
Pourriez-vous expliquer ce que vous voulez dire avec POD?
Jonas Stein
2
@JonasStein POD est un ensemble bien défini de règles relatives aux structures de données simples (plutôt qu'aux classes complètes). Lisez la FAQ pour en savoir plus: stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506
2
@Sergey, le constructeur par défaut de A serait toujours appelé.
Vassilis
44

Mis à part les raisons de performances mentionnées ci-dessus, si votre classe stocke des références aux objets passés en tant que paramètres de constructeur ou si votre classe a des variables const, vous n'avez d'autre choix que d'utiliser des listes d'initialisation.

Naveen
la source
7
Il en va de même pour les membres const je crois.
Richard Corden
oui, impossible d'utiliser l'affectation pour modifier les variables const, il doit donc être initialisé.
Hareen Laks
23
  1. Initialisation de la classe de base

Une raison importante pour utiliser la liste d'initialisation du constructeur qui n'est pas mentionnée dans les réponses ici est l'initialisation de la classe de base.

Selon l'ordre de construction, la classe de base doit être construite avant la classe enfant. Sans liste d'initialisation du constructeur, cela est possible si votre classe de base a un constructeur par défaut qui sera appelé juste avant d'entrer dans le constructeur de la classe enfant.

Mais, si votre classe de base n'a qu'un constructeur paramétré, vous devez utiliser la liste d'initialisation du constructeur pour vous assurer que votre classe de base est initialisée avant la classe enfant.

  1. Initialisation de sous-objets qui n'ont que des constructeurs paramétrés

  2. Efficacité

En utilisant la liste d'initialisation du constructeur, vous initialisez vos membres de données à l'état exact dont vous avez besoin dans votre code plutôt que de les initialiser d'abord à leur état par défaut, puis de changer leur état en celui dont vous avez besoin dans votre code.

  1. Initialisation des membres de données const non statiques

Si les membres de données const non statiques de votre classe ont des constructeurs par défaut et que vous n'utilisez pas la liste d'initialisation des constructeurs, vous ne pourrez pas les initialiser à l'état prévu car ils seront initialisés à leur état par défaut.

  1. Initialisation des membres des données de référence

Les membres des données de référence doivent être initialisés lorsque le compilateur entre dans le constructeur car les références ne peuvent pas être simplement déclarées et initialisées ultérieurement. Cela n'est possible qu'avec la liste d'initialisation du constructeur.

yuvi
la source
10

À côté des problèmes de performances, il y en a un autre très important que j'appellerais la maintenabilité et l'extensibilité du code.

Si un T est POD et que vous commencez à préférer la liste d'initialisation, alors si une fois T passera à un type non-POD, vous n'aurez pas besoin de changer quoi que ce soit autour de l'initialisation pour éviter les appels de constructeur inutiles car il est déjà optimisé.

Si le type T a un constructeur par défaut et un ou plusieurs constructeurs définis par l'utilisateur et une fois que vous décidez de supprimer ou de masquer le constructeur par défaut, alors si la liste d'initialisation a été utilisée, vous n'avez pas besoin de mettre à jour le code si vos constructeurs définis par l'utilisateur parce que ils sont déjà correctement mis en œuvre.

Même chose avec les membres const ou les membres de référence, disons qu'au départ T est défini comme suit:

struct T
{
    T() { a = 5; }
private:
    int a;
};

Ensuite, vous décidez de qualifier un as const, si vous utilisiez la liste d'initialisation depuis le début, il s'agissait d'un changement de ligne unique, mais ayant le T défini comme ci-dessus, il faut également creuser la définition du constructeur pour supprimer l'affectation:

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};

Ce n'est pas un secret que la maintenance est beaucoup plus facile et moins sujette aux erreurs si le code a été écrit non pas par un "singe de code" mais par un ingénieur qui prend des décisions basées sur une réflexion plus approfondie sur ce qu'il fait.

mloskot
la source
5

Avant l'exécution du corps du constructeur, tous les constructeurs de sa classe parente puis de ses champs sont appelés. Par défaut, les constructeurs sans argument sont appelés. Les listes d'initialisation vous permettent de choisir quel constructeur est appelé et quels arguments ce constructeur reçoit.

Si vous avez une référence ou un champ const, ou si l'une des classes utilisées n'a pas de constructeur par défaut, vous devez utiliser une liste d'initialisation.

Jamal Zafar
la source
2
// Without Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  // Assume that Type is an already
                     // declared class and it has appropriate 
                     // constructors and operators
        variable = a;
    }
};

Ici, le compilateur suit les étapes suivantes pour créer un objet de type MyClass
1. Le constructeur de type est appelé en premier pour «a».
2. L'opérateur d'affectation de "Type" est appelé à l'intérieur du corps du constructeur MyClass () pour affecter

variable = a;
  1. Et puis finalement le destructeur de «Type» est appelé «a» car il sort du cadre.

    Considérons maintenant le même code avec le constructeur MyClass () avec la liste d'initialisation

    // With Initializer List
     class MyClass {
    Type variable;
    public:
    MyClass(Type a):variable(a) {   // Assume that Type is an already
                     // declared class and it has appropriate
                     // constructors and operators
    }
    };

    Avec la liste d'initialisation, les étapes suivantes sont suivies par le compilateur:

    1. Le constructeur de copie de la classe "Type" est appelé pour initialiser: variable (a). Les arguments de la liste d'initialisation sont utilisés pour copier directement la construction «variable».
    2. Le destructeur de «Type» est appelé «a» car il sort du cadre.
Rahul Singh
la source
2
Bien que cet extrait de code puisse résoudre la question, y compris une explication hors du code aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondrez à la question pour les lecteurs à l'avenir, et ces personnes pourraient ne pas connaître les raisons de votre suggestion de code. Essayez également de ne pas surcharger votre code avec des commentaires explicatifs, cela réduit la lisibilité du code et des explications! meta.stackexchange.com/q/114762/308249
davejal
2
S'il vous plaît, écrivez votre propre compréhension ou partagez simplement le lien vers la source d'origine (ici, geeksforgeeks.com) au lieu de simplement le copier-coller.
yuvi
1

Juste pour ajouter quelques informations supplémentaires pour démontrer la différence que la liste d'initialisation de membre peut faire . Dans le Leetcode 303 Range Sum Query - Immutable, https://leetcode.com/problems/range-sum-query-immutable/ , où vous devez construire et initialiser à zéro un vecteur avec une certaine taille. Voici deux implémentations et comparaisons de vitesse différentes.

Sans liste d' initialisation des membres , pour obtenir le CA, cela m'a coûté environ 212 ms .

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
    preSum = vector<int>(nums.size()+1, 0);
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

Maintenant, en utilisant la liste d'initialisation des membres , le temps pour obtenir AC est d'environ 108 ms . Avec cet exemple simple, il est bien évident que la liste d'initialisation des membres est beaucoup plus efficace . Toutes les mesures proviennent du temps de fonctionnement de LC.

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) { 
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};
Yi Wang
la source
0

Syntaxe:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

     Sample(): Sam_x(1), Sam_y(2)     /* Classname: Initialization List */
     {
           // Constructor body
     }
  };

Liste des besoins d'initialisation:

 class Sample
 {
     public:
         int Sam_x;
         int Sam_y;

     Sample()     */* Object and variables are created - i.e.:declaration of variables */*
     { // Constructor body starts 

         Sam_x = 1;      */* Defining a value to the variable */* 
         Sam_y = 2;

     } // Constructor body ends
  };

dans le programme ci-dessus, lorsque le constructeur de la classe est exécuté, Sam_x et Sam_y sont créés. Ensuite, dans le corps du constructeur, ces variables de données de membre sont définies.

Cas d'utilisation:

  1. Const et variables de référence dans une classe

En C, les variables doivent être définies lors de la création. de la même manière en C ++, nous devons initialiser la variable Const et Reference lors de la création d'objet en utilisant la liste Initialisation. si nous faisons l'initialisation après la création de l'objet (à l'intérieur du corps du constructeur), nous obtiendrons une erreur de temps de compilation.

  1. Objets membres de la classe Sample1 (base) qui n'ont pas de constructeur par défaut

     class Sample1 
     {
         int i;
         public:
         Sample1 (int temp)
         {
            i = temp;
         }
     };
    
      // Class Sample2 contains object of Sample1 
     class Sample2
     {
      Sample1  a;
      public:
      Sample2 (int x): a(x)      /* Initializer list must be used */
      {
    
      }
     };

Lors de la création d'un objet pour une classe dérivée qui appelle en interne le constructeur de classe dérivée et appelle le constructeur de classe de base (par défaut). si la classe de base n'a pas de constructeur par défaut, l'utilisateur obtiendra une erreur de temps de compilation. Pour éviter, il faut avoir soit

 1. Default constructor of Sample1 class
 2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
  1. Le nom du paramètre du constructeur de classe et le membre de données d'une classe sont identiques:

     class Sample3 {
        int i;         /* Member variable name : i */  
        public:
        Sample3 (int i)    /* Local variable name : i */ 
        {
            i = i;
            print(i);   /* Local variable: Prints the correct value which we passed in constructor */
        }
        int getI() const 
        { 
             print(i);    /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
             return i; 
        }
     };

Comme nous le savons tous, variable locale ayant la plus haute priorité puis variable globale si les deux variables ont le même nom. Dans ce cas, le programme considère la valeur «i» {variable à la fois à gauche et à droite. ie: i = i} en tant que variable locale dans le constructeur Sample3 () et la variable membre de classe (i) a été remplacée. Pour éviter, il faut utiliser soit

  1. Initialization list 
  2. this operator.
Eswaran Pandi
la source