How is member initialisation ordered?

J

Jason Heyes

Here is a program that doesn't work as intended:

#include <iostream>
using namespace std;

class MyClass
{
int bar;
int foo;
public:
MyClass(int foo_, int bar_) : foo(foo_), bar(bar_ + foo) { }
void print() const { cout << foo << " " << bar << endl; }
};

int main()
{
MyClass obj(3,6);
obj.print(); // intended to show "3 9" but doesn't
return 0;
}

When I reverse the order in which members foo and bar are declared, the
program works properly. It appears as though members are initialised in the
order in which they are declared. Is this gaurenteed? Thanks.
 
R

Russell Hanneken

Jason said:
It appears as though members are initialised in the order in which they
are declared. Is this gaurenteed?

Yes, it is. See section 12.6.2/5 of the C++ standard.
 
P

Phlip

Jason Heyes said:
Here is a program that doesn't work as intended:

#include <iostream>
using namespace std;

class MyClass
{
int bar;
int foo;
public:
MyClass(int foo_, int bar_) : foo(foo_), bar(bar_ + foo) { }
void print() const { cout << foo << " " << bar << endl; }
};

int main()
{
MyClass obj(3,6);
obj.print(); // intended to show "3 9" but doesn't
return 0;
}

When I reverse the order in which members foo and bar are declared, the
program works properly. It appears as though members are initialised in the
order in which they are declared. Is this gaurenteed? Thanks.

The rule of thumb: The compiler tries to ensure everything destructs in
order reversed from their construction. (You should match this rule among
allocations and de-allocations.)

Hence, everything constructs in a known, defined order. Inside one class,
this order is top-to-bottom for each members.

That's why lint programs will complain when members appear to construct out
of order.

Even if you obey that rule, and construct bar before foo, don't abuse your
knowledge of the mechanics to indulge in prank construction arguments.
Reading a member inside another member's constructor should be well outside
everyone's "sane subset".

Further, the code above causes undefined behavior. 'bar(bar_ + foo)' reads
the member 'foo' before it has constructed. In practice this only means
'foo' returns garbage, but in theory "undefined behavior" means anything
could happen. For example, if 'foo' somehow occupied memory not yet switched
by hardware to permit reads, the program would crash.
 
D

Dan W.

Here is a program that doesn't work as intended:

#include <iostream>
using namespace std;

class MyClass
{
int bar;
int foo;
public:
MyClass(int foo_, int bar_) : foo(foo_), bar(bar_ + foo) { }
void print() const { cout << foo << " " << bar << endl; }
};

int main()
{
MyClass obj(3,6);
obj.print(); // intended to show "3 9" but doesn't
return 0;
}

When I reverse the order in which members foo and bar are declared, the
program works properly. It appears as though members are initialised in the
order in which they are declared. Is this gaurenteed? Thanks.

Yup, order of member declaration in the class, not in the
constructor's initializer list. I think you can violate it by moving
the initializations into the body of the constructor, not that it
would be a good idea.
 
N

Norbert Riedlin

Dan W. said:
Yup, order of member declaration in the class, not in the
constructor's initializer list. I think you can violate it by moving
the initializations into the body of the constructor, not that it
would be a good idea.

No, that would be assignements. The initialization would still happen in the
order
of the declaration. But I think it would be a good idea, at least in the
case of ints
to assign them in the body rather than relying on the order of the members
in the
header file. But even better would be:
class MyClass {
....
MyClass(int foo_, int bar_) : foo(foo_), bar(bar_ + foo_) { } // note
the underscore in foo_

CU
Norbert
 
J

Jason Heyes

Norbert Riedlin said:
No, that would be assignements. The initialization would still happen in the
order
of the declaration. But I think it would be a good idea, at least in the
case of ints
to assign them in the body rather than relying on the order of the members
in the
header file. But even better would be:
class MyClass {
...
MyClass(int foo_, int bar_) : foo(foo_), bar(bar_ + foo_) { } // note
the underscore in foo_

CU
Norbert

Yes that is better. What happens when members have non-trivial
initialisation? Here is an example:

class MyClass
{
Bar bar; // Bar has constructor with a Foo object parameter
Foo foo;

Foo make_foo(std::string name); // involves some serious work

public:
MyClass(std::string name) : foo(make_foo(name)), bar(foo) { }
};

I could write

MyClass(std::string name) : foo(make_foo(name)), bar(make_foo(name)) { }

but that involves calling make_foo twice. I think I'd better declare the
members in the right order instead.
 
N

Norbert Riedlin

Yes that is better. What happens when members have non-trivial
initialisation? Here is an example:

class MyClass
{
Bar bar; // Bar has constructor with a Foo object parameter
Foo foo;

Foo make_foo(std::string name); // involves some serious work

public:
MyClass(std::string name) : foo(make_foo(name)), bar(foo) { }
};

I could write

MyClass(std::string name) : foo(make_foo(name)), bar(make_foo(name)) { }

but that involves calling make_foo twice. I think I'd better declare the
members in the right order instead.

You are right. Always declare the members in the right order, but don't
depend on it.
You might be faced with classes written by other developers or with your
classes
changed by other developers. Some compilers warn about not following the
guideline,
but others don't.

Your make_foo() function is a good starter, but, as you mustnot depend on
non-statics
in MyClass, make make_foo() static if possible. (And think twice, if it's
not possible)
Making make_foo() a non-member might even be the option I would choose (And
of
course, change the type of the parameter to const std::string&)

Hm... While thinking about this "calling make_foo() twice" in your example,
I thought
about the following "sollution":

Foo make_foo(const std::string& name) {
//...
}

class MyClass {
Bar bar; // Bar has constructor with a const Foo& parameter
Foo foo;

MyClass(const std::string& name) : bar(foo(make_foo(name))) { }
}

I think this is highly undefined behaviour, because there are two
contradicting concepts:
1. parameters are evaluated, before a call is actually performed
2. members are initialized from top to bottom

As of rule 1, first make_foo() should be called, which in turn is used to
initialize foo, which in
turn is used to initialize bar. // note: foo initialized before bar!
Rule two mandates, that bar must be initialized with foo, then foo will be
initialized.

What is happenig here?

TIA
Norbert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top