В этом конкретном случае, есть ли разница между использованием списка инициализатора элемента и присвоением значений в конструкторе? - PullRequest
88 голосов
/ 04 января 2011

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

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

и

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

спасибо ...

Ответы [ 11 ]

77 голосов
/ 04 января 2011

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

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

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) {}
};
59 голосов
/ 04 января 2011

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

18 голосов
/ 04 января 2011

Да. В первом случае вы можете объявить _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 экземпляры должны быть инициализированы в списке инициализаторов или базовые типы должны предоставлять открытые конструкторы без параметров (что делают примитивные типы).

6 голосов
/ 16 марта 2013

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

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

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

5 голосов
/ 04 января 2011

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

3 голосов
/ 04 января 2011

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

2 голосов
/ 04 января 2011

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

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

и

std::string a("hai");

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

1 голос
/ 04 января 2011

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

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

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

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

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

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

1 голос
/ 04 января 2011

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

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

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

  1. члены константного типа должны быть инициализированы до тела инициализации.
  2. После фазы 1 любая операция записи действительна только для непостоянных элементов.
0 голосов
/ 18 марта 2016

Существует разница между списком инициализации и оператором инициализации в конструкторе. Давайте рассмотрим код ниже:

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

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

...