Что это за странный синтаксис двоеточия ( ":" ) в конструкторе?

Недавно я увидел пример следующего вида:

#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

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

Ответ 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} {}

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