Что это за странный синтаксис двоеточия (":") в конструкторе? - PullRequest
306 голосов
/ 11 ноября 2009

Недавно я видел пример, подобный следующему:

#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? Не имеет смысла для меня. Возможно, кто-то мог бы просветить меня. И, кстати, есть ли какие-либо другие особенности эзотерического языка, подобные этой, которые вы никогда не найдете в обычной книге по С ++?

Ответы [ 12 ]

294 голосов
/ 15 декабря 2011
Foo(int num): bar(num)    

Эта конструкция называется Список инициализирующих элементов в C ++.

Проще говоря, инициализирует вашего члена 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 практических затрат нет.


Когда вы ДОЛЖНЫ использовать список инициализаторов участников?

Вы будете (скорее вынуждены) использовать список инициализатора участника, если:

  • В вашем классе есть референтный член
  • В вашем классе есть нестатический член 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 не имеет конструктора по умолчанию, поэтому для инициализации его члена потребуется использовать список инициализаторов членов.

Важные моменты, на которые следует обратить внимание при использовании списков инициализаторов:

Переменные-члены класса всегда инициализируются в том порядке, в котором они объявлены в классе.

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

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

192 голосов
/ 11 ноября 2009

Это список инициализации члена . Вы должны найти информацию об этом в любой хорошей C ++ книге .

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

Вывод из записи в FAQ состоит в том, что

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

14 голосов
/ 11 ноября 2009

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

Рассмотрим эти два примера:

// 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

Все дело в эффективности.

12 голосов
/ 11 ноября 2009

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

9 голосов
/ 11 декабря 2010

Это неясно, это синтаксис списка инициализации C ++

Как правило, в вашем случае x будет инициализироваться с _x, y с _y, z с _z.

8 голосов
/ 11 ноября 2009

Другой уже объяснил вам, что наблюдаемый вами синтаксис называется «список инициализатора конструктора». Этот синтаксис позволяет настраивать пользовательские инициализировать базовые подобъекты и дочерние подобъекты класса (в отличие от предоставления им возможности инициализировать по умолчанию или оставаться неинициализированным).

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

В вашем случае элемент данных имеет тип int. int не является типом класса, поэтому у него нет конструктора. Для типа int этот синтаксис означает просто "инициализировать bar значением num" и все. Это делается просто так, без участия конструкторов, поскольку, опять же, int не является типом класса, поэтому у него не может быть никаких конструкторов.

7 голосов
/ 11 ноября 2009

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

6 голосов
/ 11 ноября 2009

Это список инициализации. Он будет инициализировать члены до запуска тела конструктора. Рассмотрим

class Foo {
 public:
   string str;
   Foo(string &p)
   {
      str = p;
   };
 };

против

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' в качестве аргумента.

5 голосов
/ 11 декабря 2010

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

5 голосов
/ 11 ноября 2009

есть еще одна «выгода»

если тип переменной-члена не поддерживает нулевую инициализацию или если это ссылка (которая не может быть инициализирована нулем), у вас нет другого выбора, кроме как предоставить список инициализации

...