logo

Коли ми використовуємо список ініціалізаторів у C++?

Список ініціалізаторів використовується для ініціалізації членів даних класу. Список членів, які потрібно ініціалізувати, позначається конструктором у вигляді списку, розділеного комами та двокрапкою. Нижче наведено приклад використання списку ініціалізаторів для ініціалізації x і y класу Point.

приклад



C++






#include> using> namespace> std;> class> Point {> private>:> >int> x;> >int> y;> public>:> >Point(>int> i = 0,>int> j = 0): x(i), y(j) {}> >/* The above use of Initializer list is optional as the> >constructor can also be written as:> >Point(int i = 0, int j = 0) {> >x = i;> >y = j;> >}> >*/> >int> getX()>const> {>return> x; }> >int> getY()>const> {>return> y; }> };> int> main()> {> >Point t1(10, 15);> >cout <<>'x = '> << t1.getX() <<>', '>;> >cout <<>'y = '> << t1.getY();> >return> 0;> }>



>

>

Вихід

x = 10, y = 15>

Наведений вище код є лише прикладом синтаксису списку Initializer. У наведеному вище коді x і y також можна легко ініціалізувати всередині конструктора. Але є ситуації, коли ініціалізація членів даних усередині конструктора не працює, і потрібно використовувати Initializer List. Ось такі випадки:

1. Для ініціалізації нестатичних константних елементів даних

Члени даних const повинні бути ініціалізовані за допомогою списку ініціалізаторів. У наступному прикладі t є постійним членом даних класу Test і ініціалізується за допомогою списку ініціалізаторів. Причина ініціалізації елемента даних const у списку ініціалізатора полягає в тому, що для елемента даних const окремо не виділяється пам’ять, він згорнутий у таблиці символів, через що нам потрібно ініціалізувати його в списку ініціалізатора.

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

приклад

C++




// C++ progmram to demonstrate the use of> // initializer list to initialize the const> // data member> #include> using> namespace> std;> class> Test {> >const> int> t;> public>:> >//Initializer list must be used> >Test(>int> t):t(t) {}> >int> getT() {>return> t; }> };> int> main() {> >Test t1(10);> >cout< return 0; }>

>

>

Вихід

10>

2. Для ініціалізації еталонних елементів

Посилальні елементи мають бути ініціалізовані за допомогою списку ініціалізаторів. У наступному прикладі t є опорним членом класу Test і ініціалізується за допомогою списку ініціалізаторів.

приклад

C++


вік Салман Кхан Кхан



// Initialization of reference data members> #include> using> namespace> std;> class> Test {> >int> &t;> public>:> >Test(>int> &t):t(t) {}>//Initializer list must be used> >int> getT() {>return> t; }> };> int> main() {> >int> x = 20;> >Test t1(x);> >cout< x = 30; cout< return 0; }>

>

>

Вихід

20 30>

3. Для ініціалізації об’єктів-членів, які не мають конструктора за замовчуванням

У наступному прикладі об’єкт a класу A є членом даних класу B, а A не має конструктора за замовчуванням. Список ініціалізаторів необхідно використовувати для ініціалізації a.

приклад

C++




// C++ progmam to initialize a member object without default> // constructor> #include> using> namespace> std;> class> A {> >int> i;> public>:> >A(>int>);> };> A::A(>int> arg)> {> >i = arg;> >cout <<>'A's Constructor called: Value of i: '> << i> ><< endl;> }> // Class B contains object of A> class> B {> >A a;> public>:> >B(>int>);> };> B::B(>int> x) : a(x)> {>// Initializer list must be used> >cout <<>'B's Constructor called'>;> }> int> main()> {> >B obj(10);> >return> 0;> }>

>

>

Вихід

A's Constructor called: Value of i: 10 B's Constructor called>

Якщо клас A мав як конструктори за замовчуванням, так і конструктори з параметрами, то список ініціалізаторів не є обов’язковим, якщо ми хочемо ініціалізувати a за допомогою конструктора за замовчуванням, але необхідно ініціалізувати a за допомогою конструктора з параметрами.

4. Для ініціалізації членів базового класу

Як і пункт 3, параметризований конструктор базового класу можна викликати лише за допомогою списку ініціалізаторів.

приклад

C++




#include> using> namespace> std;> class> A {> >int> i;> public>:> >A(>int> );> };> A::A(>int> arg) {> >i = arg;> >cout <<>'A's Constructor called: Value of i: '> << i << endl;> }> // Class B is derived from A> class> B: A {> public>:> >B(>int> );> };> B::B(>int> x):A(x) {>//Initializer list must be used> >cout <<>'B's Constructor called'>;> }> int> main() {> >B obj(10);> >return> 0;> }>

>

>

Вихід

A's Constructor called: Value of i: 10 B's Constructor called>

5. Якщо ім’я параметра конструктора збігається з ім’ям елемента даних

Якщо ім'я параметра конструктора збігається з ім'ям елемента даних, тоді елемент даних повинен бути ініціалізований або за допомогою цей покажчик або Список ініціалізаторів. У наступному прикладі як ім’я члена, так і ім’я параметра для A() є i.

приклад

C++




#include> using> namespace> std;> class> A {> >int> i;> public>:> >A(>int>);> >int> getI()>const> {>return> i; }> };> A::A(>int> i) : i(i)> {> }>// Either Initializer list or this pointer must be used> /* The above constructor can also be written as> A::A(int i) {> >this->i = i;> }> */> int> main()> {> >A a(10);> >cout << a.getI();> >return> 0;> }>

>

>

Вихід

10>

6. З міркувань ефективності

Краще ініціалізувати всі змінні класу в списку ініціалізаторів замість того, щоб призначати значення всередині тіла. Розглянемо такий приклад:

приклад

C++




// 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. Змінна конструкції за замовчуванням

3. Оператор присвоєння типу викликається всередині тіла конструктора MyClass() для призначення

variable = a;>

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

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

C++




// 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. Параметризований конструктор класу Type викликається для ініціалізації: variable(a). Аргументи в списку ініціалізатора використовуються для безпосереднього копіювання змінної конструкції.
3. Деструктор Type викликається для a, оскільки він виходить за межі області видимості.

Як ми бачимо з цього прикладу, якщо ми використовуємо присвоєння всередині тіла конструктора, є три виклики функцій: конструктор + деструктор + один виклик оператора присвоєння додавання. І якщо ми використовуємо Initializer List, то буде лише два виклики функцій: конструктор копіювання + виклик деструктора. Дивіться цю публікацію для поточного прикладу цього питання.

Цей штраф за призначення буде набагато більшим у реальних програмах, де буде багато таких змінних. Завдяки птр за додавання цього пункту.

Параметр проти рівномірної ініціалізації в C++

Краще використовувати список ініціалізації з уніфікованою ініціалізацією {} замість ініціалізації параметра (), щоб уникнути проблеми звуження перетворень і неочікуваної поведінки. Це забезпечує суворішу перевірку типу під час ініціалізації та запобігає потенційним звуженим перетворенням

Код із використанням ініціалізації параметра ()

C++




#include> class> Base {> >char> x;> public>:> >Base(>char> a)> >: x{ a }> >{> >}> >void> print() { std::cout <<>static_cast><>int>>(x); }> };> int> main()> {> >Base b{ 300 };>// Using uniform initialization with {}> >b.print();> >return> 0;> }>

string.contains java

>

>

Вихід

44>

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

Код із використанням уніфікованої ініціалізації {}

Використовуючи рівномірну ініціалізацію за допомогою {} та ініціалізацію x наданим значенням a, компілятор виконає суворішу перевірку типу та видасть попередження або помилку під час компіляції, вказуючи на звужене перетворення з int на char.
Ось код із рівномірною ініціалізацією {} , що призводить до попередження, і тому його краще використовувати

C++




#include> class> Base {> >char> x;> public>:> >Base(>char> a)> >: x{ a }> >{> >}> >void> print() { std::cout <<>static_cast><>int>>(x); }> };> int> main()> {> >Base b{ 300 };>// Using uniform initialization with {}> >b.print();> >return> 0;> }>

>

>

main.cpp: In function ‘int main()’: main.cpp:17:17: error: narrowing conversion of ‘300’ from ‘int’ to ‘char’ [-Wnarrowing] 17 | Base b{ 300 }; // Using uniform initialization with {} | ^>