Endure order of construction?

R

Rune Allnor

Hi all.

I have this class that contain a number of instances of other classes:

class A {...};

class B {...};

class Spider {
public:
Spider(const *A, const *B);
private:
Spider(){};
};

class BigClass{
public:
BigClass() : A_(), B_()
{
Spider_(&A_,&B_);
}
private:
A A_;
B B_;
Spider Spider_;
};

The A and B classes contain data, while the Spider class
is supposed to maintain certain connections / relations
between the two collections of data.

The problem here is the constructor of BigClass: Both A_ and
B_ have to be fully constructed before the constructor of Spider_
can start. Even if the declarations above indicate that it is
sufficient that the adresses of A_ and B_ have been determined
at the point when Spider::Spider(...) is called, the operations
inside
Spider::Spider(...) requires that A_ and B_ have been fully
constructed.

Is there any way to ensure that things happen in the required
sequence?

Rune
 
V

Victor Bazarov

Rune said:
Hi all.

I have this class that contain a number of instances of other classes:

class A {...};

class B {...};

class Spider {
public:
Spider(const *A, const *B);
private:
Spider(){};
};

class BigClass{
public:
BigClass() : A_(), B_()
{
Spider_(&A_,&B_);

Here you create a temporary object of type 'Spider' (and it conveniently
or inconveniently goes away at the closing curly brace).

I believe you meant to do

BigClass() : A_(), B_(), Spider_(&A_, &B_) {}

which should work just fine.
private:
A A_;
B B_;
Spider Spider_;
};

The A and B classes contain data, while the Spider class
is supposed to maintain certain connections / relations
between the two collections of data.

The problem here is the constructor of BigClass: Both A_ and
B_ have to be fully constructed before the constructor of Spider_
can start. Even if the declarations above indicate that it is
sufficient that the adresses of A_ and B_ have been determined
at the point when Spider::Spider(...) is called, the operations
inside
Spider::Spider(...) requires that A_ and B_ have been fully
constructed.

Is there any way to ensure that things happen in the required
sequence?

As you declared, the members 'A_' and 'B_' are preceding 'Spider_', so
it should be fine from the language POV.

V
 
V

Victor Bazarov

Rune said:
Hi all.

I have this class that contain a number of instances of other classes:

class A {...};

class B {...};

class Spider {
public:
Spider(const *A, const *B);
private:
Spider(){};
};

class BigClass{
public:
BigClass() : A_(), B_()
{
Spider_(&A_,&B_);

Here you create a temporary object of type 'Spider' (and it conveniently
or inconveniently goes away at the closing curly brace).

I believe you meant to do

BigClass() : A_(), B_(), Spider_(&A_, &B_) {}

which should work just fine.
private:
A A_;
B B_;
Spider Spider_;
};

The A and B classes contain data, while the Spider class
is supposed to maintain certain connections / relations
between the two collections of data.

The problem here is the constructor of BigClass: Both A_ and
B_ have to be fully constructed before the constructor of Spider_
can start. Even if the declarations above indicate that it is
sufficient that the adresses of A_ and B_ have been determined
at the point when Spider::Spider(...) is called, the operations
inside
Spider::Spider(...) requires that A_ and B_ have been fully
constructed.

Is there any way to ensure that things happen in the required
sequence?

As you declared, the members 'A_' and 'B_' are preceding 'Spider_', so
it should be fine from the language POV.

V
 
M

Marcel Müller

Rune said:
I have this class that contain a number of instances of other classes:

class A {...};

class B {...};

class Spider {
public:
Spider(const *A, const *B);
private:
Spider(){};
};

class BigClass{
public:
BigClass() : A_(), B_()
{

This won't cpompile because the constructor of Spieder is private.
Spider_(&A_,&B_);

??? - you call a non-existion operator() here.
}
private:
A A_;
B B_;
Spider Spider_;
};

The A and B classes contain data, while the Spider class
is supposed to maintain certain connections / relations
between the two collections of data.

The problem here is the constructor of BigClass: Both A_ and
B_ have to be fully constructed before the constructor of Spider_
can start. Even if the declarations above indicate that it is
sufficient that the adresses of A_ and B_ have been determined
at the point when Spider::Spider(...) is called, the operations
inside
Spider::Spider(...) requires that A_ and B_ have been fully
constructed.

This is guaranteed. The construction sequence is well defined.
- base classes in order of appearance
- members in order of appearance
- the own constructor
So if you declare Spider after A and B it will be constructed after A
and B. Yo only must not depend on BigClass to be constructed before a
base class or member of it.


Marcel
 
F

Francesco S. Carta

Hi all.

I have this class that contain a number of instances of other classes:

class A {...};

class B {...};

class Spider {
public:
Spider(const *A, const *B);
private:
Spider(){};

};

class BigClass{
public:
BigClass() : A_(), B_()
{
Spider_(&A_,&B_);
}
private:
A A_;
B B_;
Spider Spider_;

};

The A and B classes contain data, while the Spider class
is supposed to maintain certain connections / relations
between the two collections of data.

The problem here is the constructor of BigClass: Both A_ and
B_ have to be fully constructed before the constructor of Spider_
can start. Even if the declarations above indicate that it is
sufficient that the adresses of A_ and B_ have been determined
at the point when Spider::Spider(...) is called, the operations
inside
Spider::Spider(...) requires that A_ and B_ have been fully
constructed.

Is there any way to ensure that things happen in the required
sequence?

You don't need to do anything to ensure that, you're already
guaranteed that those object will be fully constructed when the
processing flow enters into the body of BigClass::BigClass().

And that's true also if you don't specify ": A_(), B_()" right behind
that function body - actually, that specification is superfluous.

The order with which the objects will be called is based on the order
of their declarations in BigClass. First A_, then B_, then Spider_.

In any case, you have messed up something - have you typed the code in
the newsreader?

As Marcel correctly pointed out, you're trying to call a non-existing
"BigClass::eek:perator()(A*, B*)"

Your code should look like the following:

-------
#include <iostream>

using namespace std;

class A {};
class B {};

class Spider {
public:
Spider(const A* a, const B* b) : a(a), b(b) { }
void print() {
cout << "Spider_.a == " << a << endl;
cout << "Spider_.b == " << b << endl;
}
private:
const A* a;
const B* b;
Spider(){};
};

class BigClass{
public:
BigClass() : Spider_(&A_,&B_) { }
void print() {
cout << "&bc.A_ == " << &A_ << endl;
cout << "&bc.B_ == " << &B_ << endl;
Spider_.print();
}
private:
A A_;
B B_;
Spider Spider_;
};

int main() {
BigClass bc;
bc.print();
return 0;
}
-------

Note that you don't need to explicitly call the constructors for A and
B, they will be called automatically before of the "Spider_(&A_,&B_)"
initializer.

The print() functions have been added just to test it.

Have good time,
Francesco
 
F

Francesco S. Carta

As Marcel correctly pointed out, you're trying to call a non-existing
"BigClass::eek:perator()(A*, B*)"

Typo there, I meant "Spider::eek:perator()(A*, B*)", of course.

By the way, Rune, are you sure you want constant objects and non
constant pointers within Spider?
 
R

Rune Allnor

Typo there, I meant "Spider::eek:perator()(A*, B*)", of course.

By the way, Rune, are you sure you want constant objects and non
constant pointers within Spider?

Thanks, everyone, for the input.

I want constant pointers to non-constant objects in
the Spider class. Once A_ and B_ have been constructed,
their addresses will not change for the lifetime of
the instance of BigClass. The states of all the
variables will change, though.

Apart from that, I am a bit confused:

Is it the order of declarations of the variables in
BigClass that determines the order the constructors
will be called?

Would something change if the BigClass constructor was
called as

BigClass::BigClass() : B_(), A_() ?

And yes, I did type the code straight into the newsreader.

Rune
 
V

Victor Bazarov

Rune said:
[..]
Is it the order of declarations of the variables in
BigClass that determines the order the constructors
will be called?
Yes.

Would something change if the BigClass constructor was
called as

BigClass::BigClass() : B_(), A_() ?

No. 'A_' shall be constructed first, 'B_' shall be constructed second,
*if* they are declared in that order in the class definition. The order
of the initializers in the initializer list *does not matter*.

V
 
F

Francesco S. Carta

Thanks, everyone, for the input.

You're welcome.
I want constant pointers to non-constant objects in
the Spider class. Once A_ and B_ have been constructed,
their addresses will not change for the lifetime of
the instance of BigClass. The states of all the
variables will change, though.

Then you should declare both the Spider's constructor arguments and
its members as "A * const".

Refer to the following FAQ:
http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.5
Apart from that, I am a bit confused:

Is it the order of declarations of the variables in
BigClass that determines the order the constructors
will be called?

Would something change if the BigClass constructor was
called as

BigClass::BigClass() : B_(), A_() ?

And yes, I did type the code straight into the newsreader.

Fine, but until you get really accustomed with C++, it's better to
post tested code - or, at least, add a note about that, telling that
the code was typed on the fly. You risk to raise misunderstanding and
harsh replies, otherwise. Luckily, this wasn't the case, but a couple
of "bugs" in your original code would have been not there if you tried
to compile it.

Take your time and dig through all the FAQs. They're really worth it,
lots of things will become clearer then.

Cheers,
Francesco
 
J

James Kanze

This is guaranteed. The construction sequence is well defined.

-- Virtual base classes (in order of appearance in a left most
depth first search), always invoked by the most derived
class (and only the most derived class) before anything
else.
- base classes in order of appearance

You mean "direct non-virtual base classes". Virtual base
classes will already have been constructed, and non-direct base
classes (other than virtual) will be constructed by the class
which inherits directly from them.
- members in order of appearance
- the own constructor
So if you declare Spider after A and B it will be constructed
after A and B. Yo only must not depend on BigClass to be
constructed before a base class or member of it.

Your description is fine as long as no virtual inheritance is
involved. Virtual inheritance complicates things a little.
 
F

Francesco S. Carta

You're welcome.


Then you should declare both the Spider's constructor arguments and
its members as "A * const".

Refer to the following FAQ:http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.5

I forgot to add one thing. From the functional point of view, using a
const pointer to a non const object is equal to just using a
reference, only the syntax used to access those members changes.

It means that my example, based on your code, could be rewritten as:
-------
#include <iostream>

using namespace std;

class A {};
class B {};

class Spider {
public:
Spider(A& a, B& b) : a(a), b(b) { }
void print() {
cout << "&Spider_.a == " << &a << endl;
cout << "&Spider_.b == " << &b << endl;
}
private:
A& a;
B& b;
};

class BigClass{
public:
BigClass() : Spider_(A_,B_) { }
void print() {
cout << "&bc.A_ == " << &A_ << endl;
cout << "&bc.B_ == " << &B_ << endl;
Spider_.print();
}
private:
A A_;
B B_;
Spider Spider_;

};

int main() {
BigClass bc;
bc.print();
return 0;
}
-------

Considerations should be made about the fact that using pointers can
give a hint about the locality of the data.

The code inside of Spider's members will have to use the "->" member
access syntax, recalling to the coder that those objects - in this
very case - do not directly belong to Spider.

But those considerations happen to fall under the "tastes" category,
and many - probably most - C++ coders will prefer references over
const pointers to non-const objects, also because the member access
syntax will be more compact and faster to type.

Just another two cents added.

Have good time,
Francesco
 

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,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top