Reputation: 3056
I have a class B
that requires an instance of class A
to be constructed:
class B
{
B(A* a); // there is no default constructor
};
Now I want to create a class that contains B
as a member, so I also need to add A
as a member and provide it to B
's constructor:
class C
{
C() : a(), b(&a) {}
A a; // 1. initialized as a()
B b; // 2. initialized as b(&a) - OK
};
But the problem is that if someone occasionally changes the order of the variables definition in the class, it will break
class C
{
C() : a(), b(&a) {}
B b; // 1. initialized as b(&a) while "a" uninitialized
A a; // too late...
};
Is there a good way to resolve this without modifying the classes A
and B
? Thanks.
Upvotes: 10
Views: 1612
Reputation: 14212
Is there a good way to resolve this without modifying the classes A and B?
Turn on compiler warnings; for gcc, this is -Wreorder (which is included in -Wall):
cc1plus: warnings being treated as errors t.cpp: In constructor 'A::A()': Line 3: warning: 'A::y' will be initialized after Line 3: warning: 'int A::x' Line 2: warning: when initialized here
Alternatively, use a lint-like tool that detects this.
But the problem is that if someone occasionally changes the order of the variables definition in the class…
Why would they do this? I suspect you're worrying too much about what might happen. Even so, you can leave a comment in the class:
A a; // Must be listed before member 'b'!
B b;
Don't underestimate the force of well-placed comments. :) Then allow someone who purposefully ignores them to get what they deserve; you are using C++, after all.
Upvotes: 7
Reputation: 361612
Use the well-known C++ idiom called Base-from-Member to solve this problem.
Define a base class as,
class C_Base
{
A a; //moved `A a` to the base class!
C_Base() : a() {}
};
class C : public C_Base
{
C() : b(&a) {}
B b; // 1. initialized as b(&a) while "a" uninitialized
//A a; // too late...
};
Now, a
is guaranteed to be initialized before b
.
Upvotes: 5
Reputation: 1553
I'm not sure how much control you have over the implementation and structure of C but is it necessary to use the objects themselves in class C? Could you redefine the class to use pointers instead and then move them from the initialization list, e.g.
class C
{
C()
{
a = new A;
b = new B(a);
}
~C() {delete a; delete b;}
A* a;
B* b;
};
This avoids the issue of order in the declaration, but gives you the new issue of ensuring they're created correctly. Also, if you create A LOT of C's very often, an initialization list is slightly faster.
Upvotes: 0
Reputation: 103733
Store b in a unique_ptr, and set it in the body, not in the initializer list:
class C
{
C() :a() {
b = std::unique_ptr<B>(new B(&a));
}
A a;
std::unique_ptr<B> b;
};
Upvotes: 2
Reputation: 74410
The problem is that you are shooting yourself in the foot with the third example. In C++ the order of member variables in a class/struct matters. No matter how you go about solving your particular problem, if you pass uninitialized data to a constructor due to poor class design / member layout, you will be working with unitialized data and possibly get undefined behavior, depending on the sort of code in place.
To address your particular example, if B
really requires an A
and the relationship is one to one, why not create a new class AB
that has both an A
object and a B
object in the right order and pass the address of A
to B
. That is:
class AB
{
public:
AB():b_(&a_) {}
private:
A a_;
B b_;
};
now class C
can avoid the ordering problem by using AB
instead of A
and B
:
class C
{
public:
...
private:
AB ab_;
};
As forementioned, this of course assumes a 1:1 relationship between A
and B
. If an A
object can be shared by many B
objects, things get more complicated.
Upvotes: 0
Reputation: 372982
One option would be to not explicitly store the A, but instead to use dynamic allocation to create a new A to store in the B:
class C {
public:
C() : b(new A) {
// handled in initialization list
}
private:
B b;
};
Since this guarantees that the A is created before the B, this should prevent this problem from ever occurring.
Upvotes: 0