web-dev-qa-db-fra.com

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

Je préfère utiliser les listes d'initialisation des membres avec mes constructeurs ... mais j'ai depuis longtemps oublié les raisons de cela ... 

Utilisez-vous des listes d’initialisation de membres dans vos constructeurs? Si oui, pourquoi? Si non pourquoi pas 

182
paxos1977

Pour les membres de POD class, 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 B appellera le constructeur par défaut de A, puis initialisera a.x à 3. Un meilleur moyen serait que le constructeur de B appelle directement le constructeur de A dans la liste d'initialisation:

B()
  : a(3)
{
}

Cela n’appellerait que le constructeur A's A(int) et non son constructeur par défaut. Dans cet exemple, la différence est négligeable, mais imaginons que le constructeur par défaut de A 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 variable membre const, 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;
};
241
Adam Rosenfield

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

40
Naveen
  1. Initialisation de la classe de base

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

Conformément à l'ordre de construction, la classe de base doit être construite avant la classe enfant. Sans liste d'initialisation de 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.

Toutefois, si votre classe de base a uniquement 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 n'ayant que des constructeurs paramétrés

  2. Efficacité

À l'aide de 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 à leur état par défaut, puis de changer leur état pour 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'initialiseurs de 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 de données de référence

Les membres de 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. Ceci n'est possible qu'avec la liste d'initialisation du constructeur.

14
yuvi

En plus 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 plus besoin de modifier l'initialisation pour éviter les appels de constructeur inutiles car elle est déjà optimisée.

Si le type T a un constructeur par défaut et un ou plusieurs constructeurs définis par l'utilisateur et que vous décidez une fois de supprimer ou de masquer le constructeur par défaut, vous n'avez pas besoin de mettre à jour le code si les constructeurs définis par l'utilisateur sont mis à jour. ils sont déjà correctement implémentés.

Idem avec les membres const ou les membres de référence, disons au départ que T est défini comme suit:

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

Ensuite, vous décidez de qualifier un en tant que const. Si vous utilisiez la liste d'initialisation à partir du début, il ne s'agissait que d'un changement de ligne, mais si le T est défini comme ci-dessus, il faut également Dig pour définir la définition du constructeur afin de supprimer l'attribution:

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 pour personne que la maintenance est beaucoup plus facile et moins sujette aux erreurs si le code a été écrit non par un "singe du code" mais par un ingénieur qui prend des décisions en se basant sur une réflexion plus approfondie de ce qu’il fait.

8
mloskot

Avant que le corps du constructeur ne soit exécuté, tous les constructeurs de sa classe parent, 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.

5
Jamal Zafar
// 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 s’appelle d’abord pour “a”. 
2. L’opérateur d’assignation de «Type» est appelé à l’intérieur du corps du constructeur MyClass () pour affecter 

variable = a;
  1. Et finalement, destructeur de "Type" est appelé "a" car il sort du cadre.

    Considérons maintenant le même code avec le constructeur MyClass () avec Initializer List

    // 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é pour "a" car il sort du cadre.
2
Rahul Singh

Juste pour ajouter quelques informations supplémentaires pour montrer à quel point la liste d'initialisation des membres peut faire la différence. Dans le code 303 Range Sum Query - Immutable, https://leetcode.com/problems/range-sum-query-immutable/ , où vous devez construire et initialiser à zéro un vecteur de certaine taille. Voici deux mises en œuvre et comparaison de vitesse différentes.

Sans la liste d'initialisation des membres, pour obtenir AC 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 d'obtention de CA est d'environ 108 ms. Avec cet exemple simple, il est évident que la liste d'initialisation member est beaucoup plus efficace. Toutes les mesures sont à partir du temps d'exécution 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];
}
};
0
Yi Wang

Syntaxe:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

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

Besoin de la liste 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 membres sont définies.

Cas d'utilisation:

  1. Variables const et référence dans une classe

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

  1. Objets membres de la classe Sample1 (base) n'ayant 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 de l'objet pour la classe dérivée, qui appelle en interne le constructeur de la classe dérivée et le constructeur de la 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, nous devons 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 la classe et le membre de la classe d’une donnée 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, la variable locale a la priorité la plus élevée, puis la variable globale si les deux variables portent le même nom. Dans ce cas, le programme considère la valeur "i" {les variables gauche et droite. i.e: i = i} en tant que variable locale dans le constructeur Sample3 () et la variable membre de la classe (i) a été remplacée. Pour éviter, nous devons utiliser soit 

  1. Initialization list 
  2. this operator.
0
Eswaran Pandi