3

In the scenario:

class A : public B {
private:
   C m_C; 
public:
   A();
}

is

A::A() : 
   m_C(5),
   B(m_C) 
{} 

legal? Would B::B(m_C) be invoked after C::C(int)? If so, what should I do to avoid it?


How did this came about in practice:

class MyValidator : public QRegExpValidator {
private:
    QRegExp myRegExp;
public:
    MyValidator(); 
    virtual void fixup(QString &f); 
}

MyValidator::MyValidator() 
    QRegExpValidator(QRegExp("foo")); 
{}

void MyValidator::fixup(QString &f){ 
    // A QRegExp("foo") is also needed here. 
}

I already discovered the

const QRegExp & QRegExpValidator::regExp() const; 

Which alleviates the need to keep my own reference to myRegExp, so my particular problem is solved..

what remains, is what would be the best pattern if QRegExpValidator did not have such feature to retrieve it's initializer.. manually plumbing all the functions to a member class, instead of inheritance?

jxh
  • 69,070
  • 8
  • 110
  • 193
qdot
  • 6,195
  • 5
  • 44
  • 95
  • B is initialized before m_C – user3159253 Aug 07 '14 at 15:22
  • 1
    This usually gives you a warning in most compilers, which would have clued you in to the answer. – Simple Aug 07 '14 at 15:24
  • @40two - not quite, there is no data dependency in that example.. so the compiler is free to chose whichever order. – qdot Aug 07 '14 at 15:26
  • @Simple - yup, but compilers often warn on legal, just ambiguous code.. Does any of you have a standards reference to this? – qdot Aug 07 '14 at 15:27
  • 1
    See 12.6.2/10 [class.base.init]: `— Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers). — Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).` – Simple Aug 07 '14 at 15:31
  • It is legal to specify initializers in whatever order you choose -- but the compiler will *ignore* the order in which you specify them, and initialize bases & members in the order defined by the STandard. – John Dibling Aug 07 '14 at 15:31
  • @qdot "No" goes for the question in the title, the standard specifies the order of initialization, first Base classes then members then call to the constructor. – 101010 Aug 07 '14 at 15:34
  • @Simple - ok, it answers this question, but.. for my learning, would declaring `m_C` as `static C m_C` ensure that `m_C` gets created prior to `B::B(C)` ? – qdot Aug 07 '14 at 15:34
  • @qdot that would initialise it during program startup and completely changes the semantics. You would need to move the initialisation out of the constructor. – Simple Aug 07 '14 at 15:35
  • @qdot No. A global instance of B could be created before the global m_C. – Neil Kirk Aug 07 '14 at 15:35
  • @40two - my "no" went for the duplicate question - it's not quite. The question you refered to deals with implicit initialization of constructor parameters (invoked in the `main()` context). – qdot Aug 07 '14 at 15:35
  • @Simple Right. Thank you for taking the time to explain this.. I really need to improve my standards google-Fu. – qdot Aug 07 '14 at 15:38

3 Answers3

6

This is what the C++11 Standard has to say ([class.base.init]/10):

In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
— Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
— Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
— Finally, the compound-statement of the constructor body is executed.
[ Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. — end note ]

So, the base class is initialized before non-static data members. Your code may invoke undefined behavior if it actually uses the uninitialized data in any way.


In comments, we discussed one possible solution to your design problem is to use has-a rather than is-a. You worried that has-a would violate DRY (don't repeat yourself).

There are two ways I use to avoid WET (write everything (at least) twice) when using has-a. One is using conversion operators that allow your object to be passed to a function expecting your "derived" type. The other is using a delegation operator.

class Object { /* ... */ };

class FakeDerivedObject {
    Foo foo;
    Object obj;
    //...

    // conversions
    operator Object & () { return obj; }
    operator const Object & () const { return obj; }
    operator Object * () { return &obj; }
    operator const Object * () const { return &obj; }

    // delegation
    Object * operator -> () { return &obj; }
    const Object * operator -> () const { return &obj; }
};

This way, you do not have to "re-implement" the interface of the underlying object.

FakeDerivedObject d;

d->SomeMethodOfUnderlyingObject();
SomeFunctionThatExpectsUnderlyingObject(d);
jxh
  • 69,070
  • 8
  • 110
  • 193
  • I've edited the question a bit - do you have an opinion on the best pattern if there is a class that's needed both by the parent constructor and by a method in the derivative class? – qdot Aug 07 '14 at 15:51
  • I might change the object to use containment rather than inheritance. – jxh Aug 07 '14 at 16:03
  • I was thinking about either that or a `Factory` static function and private constructors.. containment means I'd have to deal with manually plumbing all the methods.. against DRY and unreliable in the face of the `B` class changing it's definition. – qdot Aug 07 '14 at 16:06
  • You can define a conversion operator so usage of your class will behave like inheritance in most respects, unless the user of the "base" object attempts dynamic cast. – jxh Aug 07 '14 at 16:07
1

Is

A::A() : m_C(5), B(m_C) {}

legal?

Yes. You can list initializers in any order you choose.

Would B::B(m_C) be invoked after C::C(int)

No. The compiler will ignore the order you listed the initializers, and initialize bases first in the order they are listed in the class declaration, followed by members in the order they are declared in the class declaration.

Note that most compilers have a warning level which will warn you when initializers are listed in an order other than that matching how the initializers will actually be invoked. According to some coding standards, it is required to list the initializers in the order in which they will be invoked.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • 1
    Syntactically legal, semantically may be undefined if `B` tries to use the uninitialized argument. – jxh Aug 07 '14 at 16:14
0

Example:

class Base {
public:
    Base(){}
};

class Derived: public Base {
public:
    Derived();

private:
    int _v1;
    int _v2;
};

The construction order will be ever Base --> _v1 --> _v2

Massimo Costa
  • 1,864
  • 15
  • 23