Dlaczego warto korzystać z listy inicjalizacji członków?

Jestem częściowo za używaniem list inicjujących członków z moimi konstruktorami... ale już dawno zapomniałem o przyczynach tego...

Czy używasz list inicjalizacyjnych w swoich konstruktorach? Jeśli tak, to dlaczego? Jeśli nie, to dlaczego?

Author: paxos1977, 2009-05-29

8 answers

Dla członków klasy, to nie ma znaczenia, to tylko kwestia stylu. W przypadku członków klasy, które są klasami, unika się niepotrzebnego wywołania domyślnego konstruktora. Rozważyć:

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

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

W tym przypadku konstruktor dla B wywoła domyślny konstruktor dla A, a następnie zainicjalizuje a.x na 3. Lepiej byłoby, gdyby konstruktor B wywołał bezpośrednio konstruktor A na liście inicjalizatorów:

B()
  : a(3)
{
}

To by tylko wywołało A'S A(int) konstruktor, a nie jego domyślny konstruktor. W tym przykładzie różnica jest znikoma, ale wyobraź sobie, że domyślny konstruktor A zrobił więcej, takich jak przydzielanie pamięci lub otwieranie plików. Nie chciałbyś tego robić niepotrzebnie.

Ponadto, jeśli klasa nie ma domyślnego konstruktora lub masz zmienną składową const, musisz użyć listy inicjalizacyjnej:

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;
};
 226
Author: Adam Rosenfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 10:40:11

Poza względami wydajności wymienionymi powyżej, jeśli twoja klasa przechowuje odwołania do obiektów przekazywanych jako parametry konstruktora lub Twoja klasa ma zmienne const, to nie masz wyboru z wyjątkiem użycia list inicjujących.

 38
Author: Naveen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-29 16:46:51
  1. Inicjalizacja klasy bazowej

Jednym z ważnych powodów użycia konstruktora initializer list, który nie jest wymieniony w odpowiedziach, jest inicjalizacja klasy bazowej.

Zgodnie z porządkiem budowy, klasa bazowa powinna być zbudowana przed klasą potomną. Bez constructor initializer list, jest to możliwe, jeśli twoja klasa bazowa ma domyślny konstruktor, który zostanie wywołany tuż przed wejściem do konstruktora klasy potomnej.

Ale, jeśli twoja baza klasa ma tylko parametryzowany konstruktor, wtedy musisz użyć constructor initializer list, aby upewnić się, że twoja klasa bazowa jest zainicjalizowana przed klasą potomną.

  1. Inicjalizacja Podobiektów, które mają tylko parametryzowane konstruktory

  2. Efektywność

Używając constructor initializer list, inicjalizujesz członków danych do dokładnego stanu, którego potrzebujesz w kodzie, zamiast najpierw inicjalizować je do domyślnego stanu zmiana ich stanu na ten, którego potrzebujesz w kodzie.

  1. Inicjalizacja niestatycznych elementów danych const

Jeśli niestatyczne elementy danych const w twojej klasie mają domyślne konstruktory i nie używasz listy inicjalizatorów konstruktorów, nie będziesz w stanie zainicjować ich do zamierzonego stanu, ponieważ zostaną zainicjowane do stanu domyślnego.

  1. Inicjalizacja elementów danych referencyjnych

Dane referencyjne muszą być inicjalizowane, gdy kompilator wchodzi do konstruktora, ponieważ odwołania nie mogą być zadeklarowane i zainicjowane później. Jest to możliwe tylko z listą inicjalizatorów konstruktora.

 12
Author: yuvi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-27 06:33:34

Obok kwestii wydajności, jest jeszcze jedna bardzo ważna, którą nazwałbym konserwowalnością i rozszerzalnością kodu.

Jeśli T jest POD i zaczynasz preferować listę inicjalizacyjną, to jeśli raz T zmieni się na typ nie-POD, nie będziesz musiał zmieniać niczego wokół inicjalizacji, aby uniknąć niepotrzebnych wywołań konstruktora, ponieważ jest już zoptymalizowany.

Jeśli typ T ma domyślny konstruktor i jeden lub więcej konstruktorów zdefiniowanych przez użytkownika i raz decydujesz się na Usuń lub ukryj domyślny, a następnie jeśli lista inicjalizacji została użyta, nie musisz aktualizować kodu, jeśli konstruktory zdefiniowane przez użytkownika, ponieważ są one już poprawnie zaimplementowane.

To samo z członami const lub członami odniesienia, powiedzmy, że początkowo T jest zdefiniowane w następujący sposób:

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

Następnie decydujesz się zakwalifikować a jako const, jeśli chcesz użyć listy inicjalizacyjnej od początku, to była to zmiana pojedynczej linii, ale mając t zdefiniowaną jak powyżej, wymaga to również wykopania definicja konstruktora do usunięcia przypisania:

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};
Nie jest tajemnicą, że konserwacja jest o wiele łatwiejsza i mniej podatna na błędy, jeśli kod został napisany nie przez "małpę kodową", ale przez inżyniera, który podejmuje decyzje w oparciu o głębsze rozważenie tego, co robi.
 7
Author: mloskot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-19 13:30:04

Przed uruchomieniem konstruktora, wywołane są wszystkie konstruktory dla jego klasy nadrzędnej, a następnie dla jego pól. Domyślnie wywoływane są konstruktory bez argumentu. Listy inicjalizacyjne pozwalają wybrać, który konstruktor zostanie wywołany i jakie argumenty otrzyma.

Jeśli masz referencję lub pole const lub jeśli jedna z używanych klas nie ma domyślnego konstruktora, musisz użyć listy inicjalizacyjnej.

 5
Author: Jamal Zafar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-10 06:41:01
// 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;
    }
};

Tutaj kompilator wykonuje następujące kroki, aby utworzyć obiekt typu MyClass
1. Konstruktor typu nazywany jest jako pierwszy dla "a".
2. Operator przypisania typu jest wywoływany wewnątrz ciała konstruktora MyClass () do przypisania

variable = a;
  1. I w końcu Destruktor "typu" jest nazywany "a", ponieważ wychodzi poza zasięg.

    Teraz rozważ ten sam kod z konstruktorem MyClass () z listą Inicjalizacyjną

    // 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
    }
    };
    

    Z Inicjalizatorem Lista, po kolejnych krokach następuje kompilator:

    1. Konstruktor kopiujący klasy "Type" jest wywoływany do inicjalizacji: zmienna (a). Argumenty z listy inicjalizatorów są używane do bezpośredniego kopiowania konstruowania "zmiennej".
    2. Destruktor "typu" jest nazywany "a", ponieważ wychodzi poza zasięg.
 2
Author: Rahul Singh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-25 03:46:47

Składnia:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

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

Potrzeba listy inicjalizacji:

 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
  };

W powyższym programie, podczas wykonywania konstruktora klasy, tworzone są Sam_x i Sam_y. Następnie w konstruktorze definiowane są zmienne danych składowych.

Przypadki użycia:

  1. const i zmienne referencyjne w klasie

W C, zmienne muszą być zdefiniowane podczas tworzenia. tak samo w C++ musimy zainicjalizować zmienną Const i Reference podczas tworzenia obiektu przy użyciu listy Inicjalizacyjnej. jeśli wykonamy inicjalizację po utworzeniu obiektu (wewnątrz konstruktora), otrzymamy błąd czasu kompilacji.

  1. Obiekty należące do klasy Sample1 (base), które nie posiadają domyślnego konstruktora

     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 */
      {
    
      }
     };
    

Podczas tworzenia obiektu dla klasy pochodnej, który wewnętrznie wywoła konstruktor klasy pochodnej i wywoła konstruktor klasy bazowej (domyślnie). jeśli klasa bazowa nie posiada domyślnego konstruktora, użytkownik otrzyma błąd czasu kompilacji. Aby uniknąć, musimy mieć albo

 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. Nazwa parametru konstruktora klasy i członek danych klasy są takie same:

     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; 
        }
     };
    

Jak wszyscy wiemy, zmienna lokalna ma najwyższy priorytet, a następnie zmienna globalna, jeśli obie zmienne mają tę samą nazwę. W tym przypadku program rozważa wartość" i " {zmienna lewa i prawa strona. i. E: i = i} jako zmienna lokalna w konstruktorze Sample3() i zmiennej klasy(i) została nadpisana. Aby uniknąć, musimy użyj

  1. Initialization list 
  2. this operator.
 0
Author: Eswaran Pandi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-28 12:39:24

Wystarczy dodać kilka dodatkowych informacji, aby zademonstrować, ile różnicy może mieć lista inicjalizacyjna członka. In the Leetcode 303 Range Sum Query-Immutable, https://leetcode.com/problems/range-sum-query-immutable/, gdzie trzeba skonstruować i zainicjalizować do zera wektor o określonej wielkości. Oto dwie różne implementacje i porównanie prędkości.

Bez member initialization list, aby uzyskać AC kosztowało mnie około 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];
}
};

Teraz używając listy inicjalizacji członków , Czas na uzyskanie AC wynosi około 108 ms . W tym prostym przykładzie jest oczywiste, że lista inicjalizacji członków jest o wiele wydajniejsza . Cały pomiar pochodzi z czasu pracy z 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
Author: Yi Wang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-03 19:17:13