Оптимизация из-за списка инициализаторов конструктора
Конструкторы должны инициализировать все свои объекты-члены через список инициализаторов, если это возможно. Это более эффективно, чем создание конструкторы через назначение внутри тела конструктора.
Может ли кто-нибудь объяснить, почему более эффективно использовать список инициализаторов с помощью примера?
Ответы
Ответ 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;
}
код > обычно присваивание делает больше операции, чем простая копия.
вы должны учитывать и временный объект!