Подтвердить что ты не робот

Оптимизация из-за списка инициализаторов конструктора

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

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

4b9b3361

Ответ 1

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

#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 out из int. Таким образом, стоимость использования инициализатора не равна: один конструктор по умолчанию, один конструктор 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)

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

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

Из С++ FAQ:

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

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

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

Ответ 5

Чтобы предотвратить двойную инициализацию.

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;
}

Ответ 6

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

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

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

Ответ 7

Предположим, что у вас есть член данных в вашем классе, который имеет тип 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.

Ответ 8

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

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

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


  myClass::myClass( int x ) : member(x) {}
код >

в то время как здесь я m вызывает оператор = (const int &). уступка


    myClass::myClass( int x )
    {
         member = x;
    }
код >

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