Чому я віддаю перевагу використовувати список ініціалізації членів?


228

Я частково використовую списки ініціалізації членів зі своїми конструкторами ... але я давно забув причини цього ...

Чи використовуєте списки ініціалізації членів у своїх конструкторах? Якщо так, то чому? Якщо ні, то чому б ні?


3
Тут наведені причини ... https://www.geeksforgeeks.org/when-do-we-use-initializer-list-in-c/
u8it

Відповіді:


278

Для членів класу POD це не має ніякого значення, це лише питання стилю. Для членів класу, які є класами, це дозволяє уникнути зайвого дзвінка до конструктора за замовчуванням. Поміркуйте:

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

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

У цьому випадку конструктор для Bвикличе конструктор за замовчуванням для A, а потім ініціалізується a.xна 3. Кращим способом було б, щоб Bконструктор s безпосередньо покликавA конструктор у списку ініціалізатора:

B()
  : a(3)
{
}

Це лише дзвонить A «s A(int)конструктор , а не його конструктор по замовчуванню. У цьому прикладі різниця незначна, але уявіть, якщо ви зробите, що Aконструктор за замовчуванням зробив більше, наприклад розподіл пам'яті або відкриття файлів. Вам не хотілося б цього робити.

Крім того, якщо у класу немає конструктора за замовчуванням або у вас є constзмінна член, ви повинні використовувати список ініціалізатора:

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

5
обов'язковий також для важливого випадку довідки
4pie0

5
Чому б не використати "a (3);" або "a = A (3);" в тілі конструктора за замовчуванням B?
Сергій

1
Чи можете ви пояснити, що ви маєте на увазі під POD?
Йонас Штейн

2
@JonasStein POD - це чітко визначений набір правил, що стосуються простих структур даних (а не повних класів). Прочитайте FAQ для більш: stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506

2
@Sergey, конструктор за замовчуванням все одно буде називатися.
Василіс

44

Крім вищезгаданих причин продуктивності, якщо ваш клас зберігає посилання на об'єкти, передані як параметри конструктора, або ваш клас має const змінні, то у вас немає іншого вибору, крім використання списків ініціалізатора.


7
Те саме стосується членів const, я вважаю.
Річард Корден

так, не можна використовувати призначення для зміни змінних const, тому воно має бути ініціалізовано.
Hareen Laks

23
  1. Ініціалізація базового класу

Однією з важливих причин використання списку ініціалізаторів конструктора, який не згадується тут у відповідях, є ініціалізація базового класу.

Відповідно до порядку будівництва базовий клас повинен бути побудований перед дочірнім класом. Без списку ініціалізаторів конструктора це можливо, якщо ваш базовий клас має конструктор за замовчуванням, який буде викликаний безпосередньо перед входом у конструктор дочірнього класу.

Але, якщо ваш базовий клас має лише параметризований конструктор, ви повинні використовувати список ініціалізатора конструктора, щоб переконатися, що ваш базовий клас ініціалізований перед дочірнім класом.

  1. Ініціалізація субобектів, які мають лише параметризовані конструктори

  2. Ефективність

Використовуючи список ініціалізаторів конструктора, ви ініціалізуєте своїх членів даних у точному стані, який вам потрібен у вашому коді, а не спочатку ініціалізувати їх до стану за замовчуванням, а потім змінити їх стан на той, який вам потрібен у вашому коді.

  1. Ініціалізація нестатичних членів даних const

Якщо члени даних нестатичних const у вашому класі мають конструктори за замовчуванням, і ви не використовуєте список ініціалізаторів конструкторів, ви не зможете ініціалізувати їх до потрібного стану, оскільки вони будуть ініціалізовані до їхнього стану за замовчуванням.

  1. Ініціалізація членів довідкових даних

Члени посилальних даних повинні бути ініціалізовані, коли компілятор входить до конструктора, оскільки посилання не можуть бути оголошені та ініціалізовані пізніше. Це можливо лише зі списком ініціалізатора конструктора.


10

Поруч із питаннями щодо продуктивності, є ще один дуже важливий, який я б назвав кодовим ремонтом та розширенням.

Якщо T є POD і ви починаєте віддавати перевагу списку ініціалізації, то якщо один раз T зміниться на тип, який не є POD, вам не потрібно буде нічого змінювати навколо ініціалізації, щоб уникнути зайвих викликів конструктора, оскільки він уже оптимізований.

Якщо у типу T є конструктор за замовчуванням і один або кілька конструкторів, визначених користувачем, і ви одноразово вирішите видалити або приховати типовий, тоді, якщо був використаний список ініціалізації, вам не потрібно буде оновлювати код, якщо визначені користувачем конструктори, оскільки вони вже правильно виконані.

Так само з членами const або контрольними членами, скажімо, спочатку T визначається так:

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

Далі, ви вирішили кваліфікувати як const, якщо ви будете використовувати список ініціалізації з самого початку, то це була зміна одного рядка, але маючи T, визначений вище, він також повинен викопати визначення конструктора для видалення призначення:

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

Не секрет, що технічне обслуговування набагато простіше і менш схильне до помилок, якщо код був написаний не "мавпою з кодом", а інженером, який приймає рішення на основі більш глибокого розгляду того, чим він займається.


5

Перед тим, як запустити тіло конструктора, всі конструктори для його батьківського класу, а потім і для його полів викликаються. За замовчуванням викликаються конструктори без аргументів. Списки ініціалізації дозволяють вибрати, який конструктор викликається та які аргументи отримує цей конструктор.

Якщо у вас є посилання або поле const, або якщо один із використовуваних класів не має конструктора за замовчуванням, ви повинні використовувати список ініціалізації.


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

Тут компілятор виконує наступні кроки для створення об'єкта типу MyClass
1. Конструктор типу викликається спочатку для "a".
2. Оператор присвоєння "Type" викликається всередині корпусу конструктора MyClass () для призначення

variable = a;
  1. І тоді нарешті деструктор "Тип" викликається "а", оскільки він виходить за межі сфери.

    Тепер розглянемо той самий код із конструктором MyClass () зі списком Initializer

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

    У списку ініціалізаторів компілятор виконує наступні кроки:

    1. Конструктор копіювання класу "Тип" викликається для ініціалізації: змінної (a). Аргументи у списку ініціалізатора використовуються для безпосереднього копіювання конструкції "змінної".
    2. Деструктор "Тип" називається "a", оскільки він виходить за межі сфери.

2
Хоча цей фрагмент коду може вирішити питання, у тому числі пояснення з коду дійсно допомагає покращити якість вашої публікації. Пам'ятайте, що ви відповідаєте на запитання читачів у майбутньому, і ці люди можуть не знати причини вашої пропозиції щодо коду. Будь ласка, намагайтеся не переповнювати свій код пояснювальними коментарями, це зменшує читабельність і коду, і пояснень! meta.stackexchange.com/q/114762/308249
davejal

2
Будь ласка, напишіть власне розуміння або просто поділіться посиланням на оригінальне джерело (тут, geeksforgeeks.com), а не просто скопіюйте його.
yuvi

1

Просто додайте додаткову інформацію, щоб продемонструвати, наскільки різницю може скласти список ініціалізації членів . У leetcode 303 Query Range Sum Query - Immutable, https://leetcode.com/problems/range-sum-query-immutable/ , де потрібно сконструювати та ініціалізувати нуль вектора з певним розміром. Ось дві різні варіанти реалізації та порівняння швидкості.

Без списку ініціалізації членів , щоб отримати AC, це коштувало мені близько 212 мс .

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

Тепер, використовуючи список ініціалізації членів , час отримати AC становить близько 108 мс . З цього простого прикладу цілком очевидно, що список ініціалізації членів є набагато ефективнішим . Всі вимірювання проводяться з часу роботи від 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

Синтаксис:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

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

Список необхідності ініціалізації:

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

у програмі вище, коли конструктор класу виконується, створюються Sam_x та Sam_y . Потім в тілі конструктора визначаються змінні даних цих членів.

Користувачі:

  1. Змінні Const та Reference у класі

В C змінні повинні бути визначені під час створення. аналогічно в C ++, ми повинні ініціалізувати змінну Const і Reference під час створення об'єкта, використовуючи список ініціалізації. якщо ми зробимо ініціалізацію після створення об'єкта (Всередині корпусу конструктора), ми отримаємо помилку часу компіляції.

  1. Об'єкти-учасники класу Sample1 (базовий), які не мають конструктора за замовчуванням

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

Під час створення об'єкта для похідного класу, який буде внутрішньо викликати конструктор похідного класу та викликає конструктор базового класу (за замовчуванням). якщо базовий клас не має конструктора за замовчуванням, користувач отримає помилку часу компіляції. Щоб уникнути, ми повинні мати будь-яке

 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. Ім'я параметра параметра конструктора класу та член даних класу однакові:

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

Як ми всі знаємо, локальна змінна, що має найвищий пріоритет, ніж глобальна змінна, якщо обидві змінні мають однакову назву. У цьому випадку програма враховує значення "i" {і ліву, і праву бічну змінну. тобто: i = i}, оскільки локальна змінна в конструкторі Sample3 () та змінна елемента класу (i) переоцінили. Щоб уникнути, ми повинні використовувати будь-яке

  1. Initialization list 
  2. this operator.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.