Roman L
Roman L

Reputation: 3056

dependent classes as other class members

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

Answers (6)

Fred Nurk
Fred Nurk

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

Sarfaraz Nawaz
Sarfaraz Nawaz

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

spbots
spbots

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

Benjamin Lindley
Benjamin Lindley

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

Michael Goldshteyn
Michael Goldshteyn

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

templatetypedef
templatetypedef

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

Related Questions