How to initialize a const field in constructor?
Asked Answered
J

7

83

Imagine I have a C++ class Foo and a class Bar which has to be created with a constructor in which a Foo pointer is passed, and this pointer is meant to remain immutable in the Bar instance lifecycle. What is the correct way of doing it?

In fact, I thought I could write like the code below but it does not compile..

class Foo;

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

class Foo {
public:
  int a;
};

Any suggestion is welcome.

Jemappes answered 14/9, 2009 at 20:22 Comment(0)
H
105

You need to do it in an initializer list:

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

(Note that I renamed the incoming variable to avoid confusion.)

Housebound answered 14/9, 2009 at 20:24 Comment(13)
+1, although I've recently learned here that variables starting with underscore now officially reserved ;-)Theressathereto
Only if they are followed by an uppercase letter.Boucher
or are in namespace scope! Or are followed by another underscore. So yeah, it's technically legal in this case, but I'd say it's easier to just pretend they're reserved, and not use them at all. :)Kauffmann
jalf, but I like them. I'd rather break the law.Theressathereto
@hacker: I used to think so, too. Got bitten. Nowadays I find a trailing underscore just as fine.Show
About the rename, it is one of the few places where you can write the same name twice in an expression with different meaning: 'Bar(Foo* foo) : foo(foo) {}' will work appropriatedly as in an initialization list the first foo must be a base class or an attribute of the current class, but inside the parenthesis the parameter hides the attribute and thus 'foo' is the incoming parameter there.Antirrhinum
@dribeas: While the compiler might accept something like this, I wouldn't. Not everything should be done that could be done.Show
@Show I think you should word you last sentence like this: Not everything that could be done should be done.Medicare
@MichaelKrelin-hacker It is reserved in the global scope not in a class scope. citation from N3337 17.6.4.3.2 Global names [global.names] Certain sets of names and function signatures are always reserved to the implementation: — Each name that contains a double underscore _ _ or begins with an underscore followed by an uppercase letter (2.12) is reserved to the implementation for any use. — Each name that begins with an underscore is reserved to the implementation for use as a name in the global namespace.Medicare
So this is only possible with the initializer list, and therefore not possible at all before C++ 11?Chenoweth
Initializer lists have been around since forever. en.cppreference.com/w/cpp/language/initializer_listHousebound
What if I am writing e.g. a class that does some multiprecision computation and I need to precompute certain values in the constructor, AND those members must be const, AND some of them are computed FROM the others? Can I use an already initialized member in subsequent member initializations?Undernourished
@SzczepanHołyszewski Yes, but be careful. The order in the initializer list is not the order that the elements in the initializer list are called. The order they are called is determined by the order of the member variables in the class definition. So, as long as you have that in mind, so that you don't use uninitialized members to initialize other members, you should be good. I ran a test using godbolt, thinking I would at least get a warning, if not an error, when using an uninitialized member to initialize another member, and the default compiler settings on godbolt happily compiled it!Housebound
B
24

I believe you must do it in an initializer. For example:

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

As a side note, if you will never change what foo points at, pass it in as a reference:

Foo& foo;

Bar(Foo& foo) : foo(foo) {
}
Bottom answered 14/9, 2009 at 20:26 Comment(4)
+1: Use references wherever you want, pointers where you need.Antirrhinum
"if you will never change what it points to" AND "NULL is not a valid condition"!!!!!Biegel
@DavidV.Corbin well, you could always check for that NULL in the constructor body and throw an appropriate exceptionNureyev
@TomLint - What I was referring to was the choice of using a pointer or a reference when designing a parameter. Not about "what you can do" (C++deliberately allows you to "blow your whole leg off") but more about "Cognitive load" (the easy of understanding)... If I see a declaration taking a pointer, I ask "what are the conditions where it would be appropriate to pass NULL to this method parameter???" if I see it taking a reference, all te time I would have spent thinking about the ramifications of passing null would be saved for other activities.Biegel
W
18

Initializing const members and other special cases (such a parent classes) can be accomplished in the initializer list

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

Or, similarly, for parent initialization

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

class Bar : Foo {
public:
   Bar(int x) : Foo(x) {}
};
Warrior answered 14/9, 2009 at 20:25 Comment(0)
P
6

You need to initialize foo in the initializer list.

class Bar {
    Foo* const foo;
  public:
    Bar(Foo* f) : foo(f) {...}
};
Pithy answered 14/9, 2009 at 20:25 Comment(0)
D
2

if you need some calculations before set member use const_cast:

class Bar {
public:
    Foo * const foo;
    Bar(Foo* foo) :foo(nullptr) {
        //some calculations ...
        const_cast<Foo * &>(this->foo) = foo;
    }
};
Diao answered 13/5 at 16:24 Comment(0)
K
0

Use a reference:

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

You can then refer to foo easily in Bar:

foo.doSomething();
Kala answered 14/9, 2009 at 20:24 Comment(4)
I vote as "negative" because if it was the only answer, I could have wrongly thought that using a reference was the only way to achieve it, while instead as the other answer shows, the trick is the initializer list.Jemappes
IMHO, references are much more elegant in this case because the pointer mustn't get changed at all after it gets initialized :)Kala
@hacker I just said what I believe is a better way to do the same thing.Kala
I think this answer is undervalued. If you want to refer to an object and the address of the object being referenced must never change, a reference is far better than a const pointer. On the other hand, perhaps you want to point to a const object? That's something else, with a slightly different syntax.Fortier
A
0

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

Apparition answered 14/9, 2009 at 20:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.