Что это за странный синтаксис двоеточия ( ":" ) в конструкторе?
Недавно я увидел пример следующего вида:
#include <iostream>
class Foo {
public:
int bar;
Foo(int num): bar(num) {};
};
int main(void) {
std::cout << Foo(42).bar << std::endl;
return 0;
}
Что означает этот странный : bar(num)
? Кажется, что это инициализирует переменную-член, но я никогда не видел этот синтаксис раньше. Он выглядит как вызов функции/конструктора, но для int
? Не имеет смысла для меня. Возможно, кто-то мог просветить меня. И, между прочим, есть ли какие-либо другие эзотерические языковые функции, подобные этому, вы никогда не найдете в обычной книге на С++?
Ответы
Ответ 1
Это список инициализации членов. Вы должны найти информацию об этом в любой хорошей книге на С++.
Вы должны в большинстве случаев инициализировать все объекты-члены в списке инициализации члена (однако обратите внимание на исключения, перечисленные в конце раздел FAQ).
Пункт выгрузки из пункта часто задаваемых вопросов состоит в том, что
При прочих равных условиях ваш код будет работать быстрее, если вы используете списки инициализации, а не назначение.
Ответ 2
Foo(int num): bar(num)
Эта конструкция называется списком инициализаторов Member в С++.
Проще говоря, он инициализирует ваш член bar
значением num
.
В чем разница между инициализацией и присваиванием внутри конструктора?
Инициализация члена:
Foo(int num): bar(num) {};
Назначение участников:
Foo(int num)
{
bar = num;
}
Существует существенное различие между Инициализацией члена с использованием списка инициализаторов членов и присвоением ему значения внутри тела конструктора.
Когда вы инициализируете поля через список инициализаторов Member, конструкторы будут вызываться один раз, и объект будет создан и инициализирован за одну операцию.
Если вы используете назначение, тогда поля будут сначала инициализированы конструкторами по умолчанию, а затем переназначены (через оператор присваивания) с фактическими значениями.
Как вы видите, в последнем есть дополнительные накладные расходы на создание и назначение, что может быть значительным для пользовательских классов.
Cost of Member Initialization = Object Construction
Cost of Member Assignment = Object Construction + Assignment
Последнее фактически эквивалентно:
Foo(int num) : bar() {bar = num;}
В то время как первая эквивалентна просто:
Foo(int num): bar(num){}
Для встроенного (ваш пример кода) или членов класса POD практических накладных расходов нет.
Когда вам нужно использовать список инициаторов Member?
Вы имеете (скорее принудительно) использовать список инициализаторов членов, если:
- В вашем классе есть ссылочный элемент
- В вашем классе есть нестатический член const или
- У вашего члена класса нет конструктора по умолчанию или
- Для инициализации членов базового класса или
- Когда имя параметра конструктора совпадает с именем элемента данных (это НЕ ДОЛЖНО)
Пример кода:
class MyClass
{
public:
//Reference member, has to be Initialized in Member Initializer List
int &i;
int b;
//Non static const member, must be Initialized in Member Initializer List
const int k;
//Constructor’s parameter name b is same as class data member
//Other way is to use this->b to refer to data member
MyClass(int a, int b, int c):i(a),b(b),k(c)
{
//Without Member Initializer
//this->b = b;
}
};
class MyClass2:public MyClass
{
public:
int p;
int q;
MyClass2(int x,int y,int z,int l,int m):MyClass(x,y,z),p(l),q(m)
{
}
};
int main()
{
int x = 10;
int y = 20;
int z = 30;
MyClass obj(x,y,z);
int l = 40;
int m = 50;
MyClass2 obj2(x,y,z,l,m);
return 0;
}
-
MyClass2
не имеет конструктора по умолчанию, поэтому он должен быть инициализирован через список инициализаторов членов.
- Базовый класс
MyClass
не имеет конструктора по умолчанию, поэтому для инициализации его члена нужно использовать список инициализаторов членов.
Онлайн-версия примера кода.
Важные моменты для примечания при использовании списков инициализаторов членов:
Переменные класса Member всегда инициализируются в том порядке, в котором они объявлены в классе.
Они не инициализируются в том порядке, в котором они указаны в списке инициаторов Member.
Короче говоря, список инициализации Member не определяет порядок инициализации.
Учитывая вышеизложенное, всегда рекомендуется поддерживать тот же порядок членов для инициализации Member как порядок, в котором они объявлены в определении класса. Это связано с тем, что компиляторы не предупреждают о том, что оба заказа отличаются друг от друга, но относительно новый пользователь может путать список инициализаторов членов как порядок инициализации и записать код, зависящий от этого.
Ответ 3
Эта инициализация конструктора. Это правильный способ инициализации элементов в конструкторе класса, поскольку он запрещает вызов конструктора по умолчанию.
Рассмотрим два примера:
// Example 1
Foo(Bar b)
{
bar = b;
}
// Example 2
Foo(Bar b)
: bar(b)
{
}
В примере 1:
Bar bar(); // default constructor
bar = b; // assignment
В примере 2:
Bar bar(b) // copy constructor
Все об эффективности.
Ответ 4
Это называется списком инициализации. Это альтернативный способ инициализации членов класса. Есть преимущества использования этого вместо простого назначения новых значений членам в теле конструктора, но если у вас есть члены класса, которые являются константами или ссылками, они должны быть инициализированы.
Ответ 5
Это не скрыто, это Синтаксис списка инициализации С++
В принципе, в вашем случае x
будет инициализироваться _x
, y
с помощью _y
, z
с помощью _z
.
Ответ 6
Другой уже объяснил вам, что синтаксис, который вы наблюдаете, называется "список инициализаторов конструктора". Этот синтаксис позволяет настраивать базовые подобъекты и субобъекты членов этого класса (в отличие от разрешения инициализировать по умолчанию или оставаться неинициализированным).
Я просто хочу отметить, что синтаксис, который, как вы сказали, "выглядит как вызов конструктора", не обязательно является вызовом конструктора. В языке С++ синтаксис ()
- это всего лишь одна стандартная форма синтаксиса инициализации. Это интерпретируется по-разному для разных типов. Для типов классов с определяемым пользователем конструктором это означает одно (это действительно вызов конструктора), для типов классов без определяемого пользователем конструктора это означает другую вещь (так называемый инициализация значения) для пустого ()
), а для типов неклассов он снова означает нечто другое (поскольку типы некласса не имеют конструкторов).
В вашем случае элемент данных имеет тип int
. int
не является типом класса, поэтому он не имеет конструктора. Для типа int
этот синтаксис означает просто "инициализировать bar
со значением num
" и что он. Это делается прямо так, без каких-либо конструкторов, поскольку, опять же, int
не является типом класса, поэтому он не может иметь никаких конструкторов.
Ответ 7
Я не знаю, как вы могли это пропустить, это довольно просто. Это синтаксис для инициализации переменных-членов или конструкторов базового класса. Он работает для простых старых типов данных, а также для объектов класса.
Ответ 8
Это список инициализации. Он инициализирует элементы до того, как будет запущен тело конструктора.
Рассмотрим
class Foo {
public:
string str;
Foo(string &p)
{
str = p;
};
};
vs
class Foo {
public:
string str;
Foo(string &p): str(p) {};
};
В первом примере str будет инициализироваться конструктором без аргументов
string();
перед телом конструктора Foo. Внутри конструктора foo
string& operator=( const string& s );
будет вызываться на 'str', как вы делаете str = p;
Во втором примере str будет инициализироваться непосредственно
вызов его конструктора
string( const string& s );
с 'p' в качестве аргумента.
Ответ 9
Вы правы, это действительно способ инициализации переменных-членов. Я не уверен, что для этого есть много пользы, кроме как ясно выражая, что это инициализация. Наличие "bar = num" внутри кода может быть легко перемещено, удалено или неверно истолковано.
Ответ 10
есть еще одно "преимущество"
если тип переменной-члена не поддерживает инициализацию нуля или если ее ссылка (которая не может быть инициализирована нулем), то у вас нет выбора, кроме как предоставить список инициализации
Ответ 11
Это список инициализации для конструктора. Вместо стандартного построения x
, y
и z
, а затем присваивая им значения, полученные в параметрах, эти члены будут инициализированы этими значениями сразу же с места в карьер. Это может показаться не очень полезным для float
s, но это может быть довольно многопользовательский режим с настраиваемыми классами, которые дороги для построения.
Ответ 12
Не упоминается еще в этом потоке: поскольку С++ 11, список инициализаторов членов может использовать инициализацию списка (ака. "равномерная инициализация", "согласованная инициализация" ):
Foo(int num): bar{num} {}
который имеет ту же семантику, что и инициализация списка в других контекстах.