У цьому конкретному випадку, чи є різниця між використанням списку ініціалізаторів членів та призначенням значень у конструкторі?


90

Внутрішньо та щодо сформованого коду, чи справді існує різниця між:

MyClass::MyClass(): _capacity(15), _data(NULL), _len(0)
{
}

і

MyClass::MyClass()
{
  _capacity=15;
  _data=NULL;
  _len=0
}

Дякую...

Відповіді:


60

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


17
як сказав Річард, різниться, якщо ці значення є примітивними типами і const, списки ініціалізації є єдиним способом присвоєння значень членам const.
thbusch

11
Він працює лише так, як описано, коли змінні не є посиланнями або константами, якщо вони є константами або посиланнями, він навіть не компілюється без використання списку ініціалізації.
stefanB

77

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

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

struct aa
{
    int i;
    const int ci;       // constant member

    aa() : i(0) {} // will fail, constant member not initialized
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0) { ci = 3;} // will fail, ci is constant
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0), ci(3) {} // works
};

Приклад (неповний) клас / структура містить посилання:

struct bb {};

struct aa
{
    bb& rb;
    aa(bb& b ) : rb(b) {}
};

// usage:

bb b;
aa a(b);

І приклад ініціалізації базового класу, який вимагає параметра (наприклад, немає конструктора за замовчуванням):

struct bb {};

struct dd
{
    char c;
    dd(char x) : c(x) {}
};

struct aa : dd
{
    bb& rb;
    aa(bb& b ) : dd('a'), rb(b) {}
};

4
І якщо _capacity, _dataі _lenє типи класів без доступних конструкторів по замовчуванням?
CB Bailey

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

@stefan: Ви не можете викликати конструктори. Клас без конструктора за замовчуванням повинен бути ініціалізований у списку ініціалізатора, як і члени const.
GManNickG

2
Ви також повинні ініціалізувати посилання у списку ініціалізації
Андрій Тиличко 03

2
@stefanB: Мої вибачення, якщо я мав на увазі, що ви цього не розумієте, коли насправді це розумієте. У своїй відповіді ви вказали лише те, коли в ініціалізаторі-списку має бути названо базу або члена, ви не пояснили, що насправді є концептуальною різницею між ініціалізацією в списку ініціалізатора та призначенням в тілі конструктора, де і виникає моє непорозуміння можливо, походили з.
CB Bailey

18

Так. У першому випадку ви можете оголосити _capacity, _dataі _lenяк константи:

class MyClass
{
private:
    const int _capacity;
    const void *_data;
    const int _len;
// ...
};

Це було б важливо, якщо ви хочете забезпечити const-ness цих змінних примірника під час обчислення їх значень під час виконання, наприклад:

MyClass::MyClass() :
    _capacity(someMethod()),
    _data(someOtherMethod()),
    _len(yetAnotherMethod())
{
}

constекземпляри повинні бути ініціалізовані у списку ініціалізаторів, або базові типи повинні забезпечувати загальнодоступні конструктори без параметрів (що роблять примітивні типи).


2
Те саме стосується посилань. Якщо у вашому класі є члени посилання, їх потрібно ініціалізувати у списку ініціалізатора.
Марк Ренсом

7

Я думаю, що це посилання http://www.cplusplus.com/forum/articles/17820/ дає чудове пояснення - особливо для тих, хто знайомий із C ++.

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

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


5

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


3

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


2

Залежить від задіяних типів. Різниця подібна між

std::string a;
a = "hai";

і

std::string a("hai");

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


1

Справжня різниця зводиться до того, як компілятор gcc генерує машинний код і розміщує пам'ять. Поясніть:

  • (фаза1) Перед тілом init (включаючи список init): компілятор виділяє необхідну пам'ять для класу. Клас вже живий!
  • (фаза2) У тілі init: оскільки пам'ять виділена, кожне призначення тепер вказує на операцію над вже виходить / "ініціалізованою" змінною.

Звичайно, існують інші способи обробки членів типу const. Але щоб полегшити собі життя, автори компіляторів gcc вирішують встановити деякі правила

  1. Члени типу const повинні бути ініціалізовані перед тілом ініціалізації.
  2. Після фази 1 будь-яка операція запису дійсна лише для непостійних членів.

1

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

Якщо ви не вказали базову або нестатичну змінну члена в списку ініціалізатора конструктора, тоді цей член або база буде або ініціалізованою за замовчуванням (якщо член / база не є типом класу POD або масивом не-POD класу типи) або залишити неініціалізованим інакше.

Після введення тіла конструктора всі бази або члени будуть ініціалізовані або залишені неініціалізованими (тобто вони матимуть невизначене значення). У тілі конструктора немає можливості впливати на те, як їх слід ініціалізувати.

Можливо, вам вдасться призначити нові значення для членів у тілі конструктора, але це неможливо призначити для constчленів або членів типу класу, які були зроблені непризначуваними, і неможливо перев’язати посилання.

Для вбудованих типів та деяких визначених користувачем типів призначення в тілі конструктора може мати точно такий самий ефект, як ініціалізація з тим самим значенням у списку ініціалізатора.

Якщо вам не вдається назвати члена або базу в списку ініціалізатора, і ця сутність є посиланням, має тип класу без доступного конструктора за замовчуванням, constвизнаний користувачем, має тип POD або тип класу POD або масив типу класу POD. що містить constкваліфікованого члена (прямо чи опосередковано), тоді програма неправильно сформована.


0

Якщо ви пишете список ініціалізатора, ви робите все за один крок; якщо ви не напишете список ініціатора, ви зробите 2 кроки: один для декларації та другий для призначення значення.


0

Існує різниця між списком ініціалізації та оператором ініціалізації в конструкторі. Давайте розглянемо нижче код:

#include <initializer_list>
#include <iostream>
#include <algorithm>
#include <numeric>

class MyBase {
public:
    MyBase() {
        std::cout << __FUNCTION__ << std::endl;
    }
};

class MyClass : public MyBase {
public:
    MyClass::MyClass() : _capacity( 15 ), _data( NULL ), _len( 0 ) {
        std::cout << __FUNCTION__ << std::endl;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

class MyClass2 : public MyBase {
public:
    MyClass2::MyClass2() {
        std::cout << __FUNCTION__ << std::endl;
        _capacity = 15;
        _data = NULL;
        _len = 0;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

int main() {
    MyClass c;
    MyClass2 d;

    return 0;
}

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

Але, коли використовується MyClass2, усі члени не ініціалізуються, коли виконується перший оператор у конструкторі.

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


0

Ось такий момент, якого я не бачив, як інші посилаються на нього:

class temp{
public:
   temp(int var);
};

Клас temp не має типового ctor. Коли ми використовуємо його в іншому класі наступним чином:

class mainClass{
public:
 mainClass(){}
private:
  int a;
  temp obj;
};

код не буде компілюватися, оскільки компілятор не знає, як його ініціалізувати obj, тому що у нього є просто явний ctor, який отримує значення int, тому ми повинні змінити ctor наступним чином:

mainClass(int sth):obj(sth){}

Отже, мова йде не лише про const та посилання!

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