В этом конкретном случае есть ли разница между использованием списка инициализаторов членов и присвоением значений в конструкторе?

90

Есть ли разница между внутренне и сгенерированным кодом:

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

а также

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

Благодарность...

Стеф
источник

Ответы:

61

Если предположить, что эти значения являются примитивными типами, то нет никакой разницы. Списки инициализации имеют значение только тогда, когда у вас есть объекты в качестве членов, поскольку вместо использования инициализации по умолчанию с последующим назначением список инициализации позволяет вам инициализировать объект до его конечного значения. На самом деле это может быть заметно быстрее.

templatetypedef
источник
17
как сказал Ричард, имеет значение, если эти значения являются примитивными типами и константными, списки инициализации - единственный способ присвоить значения константным членам.
thbusch 03
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 03
@stefan: Вы не можете вызывать конструкторы. Класс без конструктора по умолчанию должен быть инициализирован в списке инициализаторов, как и члены const.
GManNickG 03
2
Вы также должны инициализировать ссылки в списке инициализации
Андрей Тыличко 03
2
@stefanB: Мои извинения, если я намекал, что вы этого не понимаете, хотя на самом деле это понимаете. В своем ответе вы только указали, когда база или член должны быть названы в списке инициализаторов, вы не объяснили, в чем на самом деле концептуальная разница между инициализацией в списке инициализаторов и назначением в теле конструктора, в чем мое недоразумение возможно, пришел из.
CB Bailey
18

Да. В первом случае вы можете объявить _capacity, _dataи _lenкак константы:

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

Это было бы важно, если вы хотите обеспечить constнадежность этих переменных экземпляра при вычислении их значений во время выполнения, например:

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

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

Ричард Кук
источник
2
То же самое и со ссылками. Если у вашего класса есть ссылочные члены, они должны быть инициализированы в списке инициализаторов.
Марк Рэнсом
7

Я думаю, что эта ссылка http://www.cplusplus.com/forum/articles/17820/ дает отличное объяснение - особенно для новичков в C ++.

Причина, по которой списки инициализаторов более эффективны, заключается в том, что в теле конструктора выполняются только присваивания, а не инициализация. Поэтому, если вы имеете дело с не встроенным типом, конструктор по умолчанию для этого объекта уже был вызван до того, как было введено тело конструктора. Внутри тела конструктора вы присваиваете значение этому объекту.

По сути, это вызов конструктора по умолчанию, за которым следует вызов оператора присваивания копии. Список инициализаторов позволяет напрямую вызывать конструктор копирования, и иногда это может быть значительно быстрее (напомним, что список инициализаторов находится перед телом конструктора)

user929404
источник
5

Я добавлю, что если у вас есть члены типа класса, у которых нет конструктора по умолчанию, инициализация - единственный способ построить ваш класс.

Фред Ларсон
источник
3

Большая разница в том, что назначение может инициализировать членов родительского класса; инициализатор работает только с членами, объявленными в текущей области действия класса.

Марк Рэнсом
источник
2

Зависит от задействованных типов. Подобная разница между

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

а также

std::string a("hai");

где вторая форма - это список инициализации, то есть имеет значение, требует ли тип аргументов конструктора или более эффективен с аргументами конструктора.

Щенок
источник
1

Настоящая разница заключается в том, как компилятор gcc генерирует машинный код и размещает память. Объясните:

  • (phase1) Перед телом инициализации (включая список инициализации): компилятор выделяет требуемую память для класса. Класс уже жив!
  • (phase2) В теле инициализации: поскольку память выделена, каждое присвоение теперь указывает операцию над уже существующей / инициализированной переменной.

Конечно, есть и другие способы обработки членов константного типа. Но чтобы облегчить себе жизнь, авторы компилятора gcc решили установить некоторые правила.

  1. Члены типа const должны быть инициализированы до тела инициализации.
  2. После фазы 1 любая операция записи действительна только для непостоянных членов.
Lukmac
источник
1

Есть только один способ инициализировать экземпляры базового класса и нестатические переменные-члены - использовать список инициализаторов.

Если вы не укажете базовую или нестатическую переменную-член в списке инициализаторов вашего конструктора, тогда этот член или база будут либо инициализированы по умолчанию (если член / база не является типом класса POD или массивом не-POD класса типы) или оставить неинициализированным в противном случае.

После ввода тела конструктора все базы или члены будут инициализированы или оставлены неинициализированными (т.е. они будут иметь неопределенное значение). В теле конструктора нет возможности влиять на то, как они должны быть инициализированы.

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

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

Если вы не можете указать член или базу в списке инициализаторов, и эта сущность является ссылкой, имеет тип класса без доступного конструктора по умолчанию, объявленный пользователем, constквалифицирован и имеет тип POD или является типом класса POD или массивом типа класса POD содержит constквалифицированного члена (прямо или косвенно), значит, программа плохо сформирована.

CB Bailey
источник
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, все члены не инициализируются при выполнении первого оператора в конструкторе.

В более позднем случае может возникнуть проблема регрессии, когда кто-то добавил код в конструктор до инициализации определенного члена.

user928143
источник
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){}

Итак, дело не только в константах и ​​ссылках!

hosh0425
источник