Оптимизация за счет списка инициализатора конструктора - PullRequest
13 голосов
/ 05 декабря 2011

Конструкторы должны инициализировать все свои объекты-члены через список инициализатора, если это возможно. Это более эффективно, чем строительство конструкторы через присваивание внутри тела конструктора.

Может ли кто-нибудь объяснить, почему более эффективно использовать список инициализаторов с помощью примера?

Ответы [ 8 ]

14 голосов
/ 05 декабря 2011

Рассмотрим эту программу:

#include <iostream>

struct A {
  A() { std::cout << "A::A()\n"; }
  A(int) { std::cout << "A::(int)\n"; }
  void operator=(const A&) { std::cout << "A::operator=(const A&)\n"; }
};

struct C1 {
  A a;
  C1(int i) { 
    a = i;
  }
};

struct C2 {
  A a;
  C2(int i)  : a(i) {}
};

int main() {
  std::cout << "How expesive is it to create a C1?\n";
  { C1 c1(7); }
  std::cout << "How expensive is it to create a C2?\n";
  { C2 c2(7); }
}

В моей системе (Ubuntu 11.10, g ++ 4.6.1) программа выдает следующие данные:

How expesive is it to create a C1?
A::A()
A::(int)
A::operator=(const A&)
How expensive is it to create a C2?
A::(int)

Теперь рассмотрим, почему он это делает. В первом случае C1::C1(int), a должны быть созданы по умолчанию, прежде чем конструктор C1 может быть вызван. Затем он должен быть назначен через operator=. В моем тривиальном примере нет доступного оператора присваивания int, поэтому мы должны создать A из целого числа. Таким образом, цена отказа от использования инициализатора: один конструктор по умолчанию, один конструктор int и один оператор присваивания.

Во втором случае, C2::C2(int), вызывается только конструктор int. Какими бы ни были затраты на конструктор A по умолчанию, очевидно, что стоимость C2:C2(int) не превышает стоимость C1::C1(int).

<ч /> Или рассмотрите эту альтернативу. Предположим, что мы добавили следующий элемент в A:

void operator=(int) { std::cout << "A::operator=(int)\n"; }

Тогда результат будет выглядеть так:

How expesive is it to create a C1?
A::A()
A::operator=(int)
How expensive is it to create a C2?
A::(int)

Сейчас вообще невозможно сказать, какая форма более эффективна. Является ли в вашем конкретном классе стоимость конструктора по умолчанию плюс стоимость назначения дороже, чем конструктор не по умолчанию? Если так, то список инициализации более эффективен. В противном случае это не так.

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

9 голосов
/ 05 декабря 2011

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

5 голосов
/ 05 декабря 2011

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

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

2 голосов
/ 05 декабря 2011

Из C ++ FAQ :

Рассмотрим следующий конструктор, который инициализирует объект-член x_ с использованием списка инициализации: Fred :: Fred (): x_ (что угодно){}Наиболее распространенным преимуществом этого является повышение производительности.Например, если выражение любого типа того же типа, что и переменная-член x_, результат любого выражения создается непосредственно внутри x_ - компилятор не создает отдельную копию объекта.Даже если типы не совпадают, компилятор обычно может лучше выполнять работу со списками инициализации, чем с присваиваниями.

Другой (неэффективный) способ построения конструкторов - это использование присваивания, например: Fred:: Fred () {x_ = что угодно;}.В этом случае выражение вызывает создание отдельного временного объекта, и этот временный объект передается в оператор присваивания объекта x_.Затем этот временный объект разрушается в;Это неэффективно.

Как будто это не так уж и плохо, есть другой источник неэффективности при использовании присваивания в конструкторе: объект-член будет полностью построен своим конструктором по умолчанию, и это может, например, выделитьнекоторый объем памяти по умолчанию или открыть какой-то файл по умолчанию.Вся эта работа может быть напрасной, если какое-либо выражение и / или оператор присваивания заставит объект закрыть этот файл и / или освободить эту память (например, если конструктор по умолчанию не выделил достаточно большой пул памяти или если он открылсянеправильный файл).

0 голосов
/ 05 декабря 2011

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

Если вы инициализируете объекты внутри тела конструктора, вы выполняете присваивание.

Пример: здесь я вызываю конструктор копирования int.


  <code>myClass::myClass( int x ) : member(x) {}

пока я вызываю оператор = (const int &). назначение


    <code>myClass::myClass( int x )
    {
         member = x;
    }

обычно присвоение выполняет больше операций, чем простая копия. Вы должны учитывать и временный объект!

0 голосов
/ 05 декабря 2011

Предположим, у вас есть член данных в вашем классе, который имеет тип std :: string. Когда конструктор этого класса выполняется, класс строки конструктора по умолчанию вызывается автоматически, потому что объекты инициализируются перед телом конструктора.

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

class Person
{
public:
    Person(string s);
    ~Person();
private:
    string name;
};


// case 1
Person::Person(string s)
{
   name = s;   // creates a temporary object
}

// case 2
Person::Person(string s):name(s) {} // whereas this will not create a temporary object.
0 голосов
/ 05 декабря 2011

Что касается типов POD, инициализация и присвоение должны быть эквивалентны, поскольку они остаются неинициализированными, если инициализация не выполняется явно, поэтому единственной операцией остается присвоение.

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

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

0 голосов
/ 05 декабря 2011

Для предотвращения двойной инициализации.

class B
{
//whatever
};

class A
{
   B _b;
public:
   A(B& b)
};

Теперь два случая:

//only initializes _b once via a copy constructor
A::A(B& b) : _b(b)
{
}

//initializes _b once before the constructor body, and then copies the new value
A::A(B& b)
{
   //_b is already initialized here
   //....
   //one extra instruction:
   _b = b;
}
...