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

Ваш вопрос очень прост - ваш код не совпадает, поэтому он не будет производить ту же сборку! Оптимизированный код зависит не только от отдельных операторов, но и от всего вокруг него. И в этом случае легко объяснить оптимизацию.

В вашем первом примере в случае 1 получается a = 1, а в случае 2 - a = 2. Компилятор может оптимизировать это, чтобы установить a = sc для этих двух случаев, что является одним оператором.

Во втором примере в случае 1 получается a = 2, а в случае 2 - a = 1. Компилятор больше не может использовать этот ярлык, поэтому он должен явно установить a = 1 или a = 2 для обоих случаев. Конечно, для этого нужно больше кода.

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

Вы можете проверить эту оптимизацию, используя код

int main()
{
    int a, sc = 1;

    switch (sc)
    {
        case 1:
        case 2:
            a = sc;
            break;
    }
}

, который также должен давать точно такой же ассемблер.

Кстати, ваш тестовый код предполагает, что sc действительно читается. Большинство современных оптимизирующих компиляторов способны обнаружить, что sc не изменяется между присваиванием и оператором switch, и заменить чтение sc постоянным значением 1. Дальнейшая оптимизация затем удалит избыточную ветвь (и) оператора switch, а затем даже назначение может быть оптимизировано, потому что на самом деле не изменяется. И с точки зрения переменной a, компилятор может также обнаружить, что a не читается в другом месте, и полностью удалить эту переменную из кода.

Если вы действительно хотите, чтобы sc читался и a был установлен, вам нужно объявить их обоих volatile. К счастью, компилятор, похоже, реализовал это так, как вы ожидали, но вы абсолютно не можете ожидать этого, когда у вас включена оптимизация.

212
задан paxos1977 30 November 2016 в 10:04
поделиться

3 ответа

For POD class members, it makes no difference, it's just a matter of style. For class members which are classes, then it avoids an unnecessary call to a default constructor. Consider:

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

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

In this case, the constructor for B will call the default constructor for A, and then initialize a.x to 3. A better way would be for B's constructor to directly call A's constructor in the initializer list:

B()
  : a(3)
{
}

This would only call A's A(int) constructor and not its default constructor. In this example, the difference is negligible, but imagine if you will that A's default constructor did more, such as allocating memory or opening files. You wouldn't want to do that unnecessarily.

Furthermore, if a class doesn't have a default constructor, or you have a const member variable, you must use an initializer list:

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;
};
263
ответ дан 23 November 2019 в 04:30
поделиться

Помимо упомянутых выше причин производительности, если ваш класс хранит ссылки на объекты, переданные как параметры конструктора, или ваш класс имеет константные переменные, тогда у вас нет другого выбора, кроме использования списков инициализаторов.

42
ответ дан 23 November 2019 в 04:30
поделиться

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

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

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

То же самое с константными членами или ссылкой члены, предположим, что изначально T определяется следующим образом:

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

Затем вы решаете квалифицировать a как const,если вы использовали бы список инициализации с самого начала, тогда это было изменение одной строки, но, если T определен, как указано выше, также потребуется копать определение конструктора, чтобы удалить назначение:

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

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

8
ответ дан 23 November 2019 в 04:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: