2

For many classes C1, C2, and so forth, the initialization looks equal, and it's just a little something differs from class to class. Hence I created a base class B that hosts the initalizations, e.g.:

class B {
  public:
    B()
    {
      // complex initializations
      int a = doSomething();
      // more complex stuff with `a`
    };

    virtual int doSomething()
    {return 2 * doSomethingHelper();}

  protected:
    virtual int doSomethingHelper() = 0;
};

class C: public B {
  protected:
    virtual int doSomethingHelper()
    {return 1;}
};

int main() {
  C c;
  return 0;
}

This code fails with

pure virtual method called
terminate called without an active exception
Aborted (core dumped)

since doSomethingHelper() is used to initialize B.

I'm wondering if there is a better design. My objectives are:

  1. Make C's user interface as easy as possible: C has no constructor arguments.

  2. Make C itself as minimalistic as possible such that other concretizations of B are short. Ideally, it only contains the doSomethingHelper.

A suggestion for a more sane design would be appreciated.

Christophe
  • 68,716
  • 7
  • 72
  • 138
Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • 2
    Most of the times you see this error, you're just missing `virtual` destructors in your inheritance hierarchies somewhere. But it's pretty hard to debug, where actually. Slicing also can get you into that situation, which is even harder to debug. – πάντα ῥεῖ Jun 23 '15 at 18:46
  • Why wouldn't what you posted work? Can you post a simplified example of your broken code that is still broken? – NathanOliver Jun 23 '15 at 18:47
  • 2
    "but for the same structure in a more complex example". You should post the code that doesn't work, not the code that does. – Barry Jun 23 '15 at 18:47
  • 2
    The difference is probably that the code causing problems calls the virtual function from a ctor or dtor of a baseclass. Still, you should distill a minimal example before asking here. – Ulrich Eckhardt Jun 23 '15 at 18:51
  • 2
    I have seen this error when a virtual member function is called before the most derived class has been initialized. For example, if you call the virtual member function in the body of the base class, or use it to initialize another member variable. Please post an [MCVE](http://stackoverflow.com/help/mcve). – R Sahu Jun 23 '15 at 18:57
  • @RSahu Thanks, your comment helped me create an MCVE. – Nico Schlömer Jun 23 '15 at 19:08

4 Answers4

2

Short answer:

Don't call virtual functions from within a constructor. Doing so will get you in trouble.


Longer answer:

The declaration C c creates and constructs, in steps, an instance of class C. That object is first constructed as a class A object, then as a class B object, and finally as a class C object. At the point that B::B() is called, there is no notion that the object will eventually be an instance of class C.

When B::B() is invoked, it calls the virtual function doSomething(), which in this case means calling B::doSomething(). There's no problem yet; that function exists. The problem is with the call to doSomethingHelper() within the body of B::doSomething(). That function is pure virtual at this point. As noted above, there is no indication that this object will eventually be an instance of class C. The function C::doSomethingHelper() cannot be called cannot be called from B::B() or A::A(). There is no A::doSomethingHelper(), no B::doSomethingHelper(), so the function doesn't exist. You're toast.


I'm wondering if there is a better design.

There are lots of better designs. The simplest is to not call doSomething() from within the constructor of class B. Move that call to the constructor of class C. Even then, calling a virtual function from within the constructor may not be a good idea. What if class D inherits from class C and overrides C::doSomethingHelper()? An instance of class D will be constructed by calling C::doSomethingHelper() rather than D::doSomethingHelper().

David Hammen
  • 32,454
  • 9
  • 60
  • 108
1

According to the standard:

10.4/6: Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined

This is because, when you construct C:

  • first the subobject B is constructed using the B() constructor. At that time, it's still B's virtual functions which are used. You get the error because at this moment, doSomethingHelper() is not defined for it.

  • Only once B() is completed will the virtual will C's virtual functions become active.

Two phased initialisation

This situation can only be avoided through a two phased initialisation: first construction, then calling an initialisation function. Not so nice and user friendly as you'd have desired.

class B {
public:
    B() { /* complex initializations */ }
    ...
protected:
    void init() {  //  the rest of the what you wanted in constructor 
        int a = doSomething();
        // more complex stuff with `a`
    }
};

The two phased initialisation could then be triggered via C's constructor:

class C {
public: 
    C() : B() {  // first B is constructed
       init();   // then the body of C's constructor is executed
    }   
 ...
 };

Variant for the two-phased initialisation

There's a small variant that you can use, to hide the two phase approach, and let more freedom for the user about defining or not their own constructor.

In B you define an auxiliary nested class:

protected:
    class initialiser {
    public: 
        initialiser(B*b) {b->init();}  // the constructor launches the second phase init
    };

In C , you just need to add a protected member variable:

class C:  public  B {
    ...
protected:
    B::initialiser bi{this}; // this triggers automaticcaly the second phase
    ...
};

The standard's rules ensure that first B is constructed and then the members of C. Demo here.

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
1

You can't use dynamic dispatching to a derived class in a constructor.

When B's constructor is running, the C subobject has not yet been created, so none of its overriden functions can be used. As B doesn't provide an implementation for doSomethingHelper, nothing sensible can be done.

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • Better written as you shouldn't make virtual calls in a destructor. You can make calls to virtual functions in a constructor so long as the virtual function is defined at the level of the class being constructed. The object in question is fully constructed as an instance of `class B` at the point the code starts executing the body of `B::B()`. – David Hammen Jun 23 '15 at 19:30
  • @DavidHammen You can call virtual functions from a constructor, but it will be a static call (with the static type of the base your constructing), not a virtual one (which would pick up the dynamic type of the full object, and cannot be done here). – Quentin Jun 23 '15 at 19:49
  • The function `B::doSomething()` doesn't know that it's being called from the constructor. That function uses dynamic dispatch to call `doSomethingHelper()`. There is no entry for that function in the object's vtable (or whatever is used in place of a vtable). – David Hammen Jun 23 '15 at 20:00
  • @DavidHammen I had overlooked `doSomething`. Thanks ! – Quentin Jun 23 '15 at 20:45
0

Move all the complexity into B::doSomething, and call that method from the end of inheritance chain, C:

class B {
  public:
    B()
    {};

    virtual int doSomething()
    {
      // complex initializations
      int a = 2 * doSomethingHelper();
      // more complex stuff with `a`
      return a;
    }

  protected:
    virtual int doSomethingHelper() = 0;
};

class C: public B {
  public:
    C(): B()
    {
      int a = doSomething();
    }

  protected:
    virtual int doSomethingHelper()
    {return 1;}
};

int main() {
  C c;
  return 0;
}

This might require you to make some of B formerly private members protected such that they can be initialized by C.

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249