Как инициализировать константное поле в конструкторе?

Представьте, что у меня есть класс C++ Foo и класс Bar, который должен быть создан с помощью конструктора, в который передается указатель Foo, и этот указатель должен оставаться неизменным в жизненном цикле экземпляра Bar. Каков правильный способ сделать это?

На самом деле, я думал, что смогу написать код, как показано ниже, но он не компилируется.

class Foo;

class Bar {
public:
    Foo * const foo;
    Bar(Foo* foo) {
        this->foo = foo;
    }
};

class Foo {
public:
  int a;
};

Любое предложение приветствуется.


person puccio    schedule 14.09.2009    source источник


Ответы (6)


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

Bar(Foo* _foo) : foo(_foo) {
}

(Обратите внимание, что я переименовал входящую переменную, чтобы избежать путаницы.)

person Jim Buck    schedule 14.09.2009
comment
+1, хотя я недавно узнал здесь, что переменные, начинающиеся с подчеркивания, теперь официально зарезервированы ;-) - person Michael Krelin - hacker; 15.09.2009
comment
Только если за ними следует заглавная буква. - person GManNickG; 15.09.2009
comment
или находятся в области пространства имен! Или сопровождаются другим символом подчеркивания. Так что да, в данном случае это технически законно, но я бы сказал, что проще просто притвориться, что они зарезервированы, и не использовать их вообще. :) - person jalf; 15.09.2009
comment
jalf, а мне они нравятся. Я лучше закон нарушу. - person Michael Krelin - hacker; 15.09.2009
comment
@hacker: Я тоже так думал. Был укушен. В настоящее время я нахожу завершающее подчеркивание таким же прекрасным. - person sbi; 15.09.2009
comment
Что касается переименования, это одно из немногих мест, где вы можете написать одно и то же имя дважды в выражении с разным значением: 'Bar(Foo* foo) : foo(foo) {}' будет работать соответствующим образом, как в списке инициализации first foo должен быть базовым классом или атрибутом текущего класса, но внутри круглых скобок параметр скрывает атрибут, и поэтому «foo» является входящим параметром. - person David Rodríguez - dribeas; 15.09.2009
comment
@dribeas: Хотя компилятор может принять что-то подобное, я бы не стал. Не следует делать все, что можно сделать. - person sbi; 15.09.2009
comment
@sbi Я думаю, вы должны сформулировать последнее предложение так: не все, что можно сделать, нужно сделать. - person Gab是好人; 05.12.2016
comment
@MichaelKrelin-hacker Он зарезервирован в глобальной области, а не в области класса. цитата из N3337 17.6.4.3.2 Глобальные имена [global.names] Определенные наборы имен и сигнатур функций всегда зарезервированы для реализации: — Каждое имя, которое содержит двойное подчеркивание _ _ или начинается с подчеркивания, за которым следует заглавная буква (2.12) зарезервирована для реализации для любого использования. — Каждое имя, начинающееся со знака подчеркивания, зарезервировано реализацией для использования в качестве имени в глобальном пространстве имен. - person Gab是好人; 05.12.2016
comment
Так что это возможно только со списком инициализаторов и, следовательно, вообще невозможно до C++ 11? - person deetz; 14.11.2017
comment
Списки инициализаторов существуют с незапамятных времен. en.cppreference.com/w/cpp/language/initializer_list - person Jim Buck; 14.11.2017
comment
Что, если я пишу, например. класс, который выполняет некоторые вычисления с множественной точностью, и мне нужно предварительно вычислить определенные значения в конструкторе, И эти члены должны быть константными, И некоторые из них вычисляются ИЗ других? Могу ли я использовать уже инициализированный член в последующих инициализациях члена? - person Szczepan Hołyszewski; 10.05.2021
comment
@SzczepanHołyszewski Да, но будь осторожен. Порядок в списке инициализаторов не совпадает с порядком вызова элементов в списке инициализаторов. Порядок их вызова определяется порядком переменных-членов в определении класса. Итак, пока вы имеете это в виду, чтобы не использовать неинициализированные члены для инициализации других членов, все должно быть хорошо. Я провел тест с использованием godbolt, думая, что получу хотя бы предупреждение, если не ошибку, при использовании неинициализированного члена для инициализации другого члена, и настройки компилятора по умолчанию для godbolt благополучно скомпилировали его! - person Jim Buck; 10.05.2021

Я считаю, что вы должны сделать это в инициализаторе. Например:

Bar(Foo* foo) : foo(foo) {
}

В качестве примечания, если вы никогда не будете менять то, на что указывает foo, передайте это как ссылку:

Foo& foo;

Bar(Foo& foo) : foo(foo) {
}
person SingleShot    schedule 14.09.2009
comment
+1: Используйте ссылки, где хотите, указатели, где вам нужно. - person David Rodríguez - dribeas; 15.09.2009

Инициализация членов const и других особых случаев (таких как родительские классы) может быть выполнена в списке инициализаторов.

class Foo {
private:
   const int data;
public:
   Foo(int x) : data(x) {}
};

Или, аналогично, для родительской инициализации

class Foo {
private:
   int data;
public:
   Foo(int x) : data(x) {}
};

class Bar : Foo {
public:
   Bar(int x) : Foo(x) {}
};
person ezpz    schedule 14.09.2009

Вам нужно инициализировать foo в списке инициализаторов.

class Bar {
    Foo* const foo;
  public:
    Bar(Foo* f) : foo(f) {...}
};
person KeithB    schedule 14.09.2009

Используйте ссылку:

Foo& foo;
Bar(Foo& f) : foo(f) { }

Затем вы можете легко обратиться к foo в Bar:

foo.doSomething();
person AraK    schedule 14.09.2009
comment
Я голосую отрицательно, потому что, если бы это был единственный ответ, я мог бы ошибочно подумать, что использование ссылки было единственным способом добиться этого, в то время как вместо этого, как показывает другой ответ, хитрость заключается в списке инициализаторов. - person puccio; 15.09.2009
comment
ИМХО, ссылки в этом случае намного элегантнее, потому что указатель вообще не должен меняться после инициализации :) - person AraK; 15.09.2009
comment
@hacker Я только что сказал, что, по моему мнению, это лучший способ сделать то же самое. - person AraK; 15.09.2009
comment
Я думаю, что этот ответ недооценен. Если вы хотите сослаться на объект, и адрес объекта, на который ссылаются, никогда не должен изменяться, ссылка намного лучше, чем постоянный указатель. С другой стороны, возможно, вы хотите указать на константный объект? Это что-то другое, с немного другим синтаксисом. - person Spacemoose; 26.02.2016

попробуй: Bar(Foo* xfoo) : foo(xfoo) {}

person John Ledbetter    schedule 14.09.2009