Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ 11 Delegated Constructor Pure Virtual Method & Function Calls -- Dangers?

Not a Duplicate of Invoking virtual function and pure-virtual function from a constructor:

Former Question relates to C++ 03, not new Constructor Delegation behavior in C++ 11, and the question does not address the mitigation of undefined behavior by using delegation to ensure proper construction before pure virtual implementations are executed.

In C++ 11, what are the dangers of invoking Pure Virtual functions in a class' constructor, during construction, but after the class/object has been "fully constructed" via constructor delegation?

Apparently, somewhere in the C++ 11 spec such a constraint exists,

Member functions (including virtual member functions, 10.3) can be called for an object under construction. Similarly, an object under construction can be the operand of the typeid operator .. - 12.6.2 #13 of the [C++ Working Draft] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf) Can't find "Fair Use" version of Published Spec.

C++11 considers an object constructed once any constructor finishes execution. Since multiple constructors will be allowed to execute, this will mean that each delegate constructor will be executing on a fully constructed object of its own type. Derived class constructors will execute after all delegation in their base classes is complete. - Wikipedia saying that this is a C++ 11 thing.

Actual C++ 11 Reference unknown.

Following Example Compiles AND RUNS in Nov CTP of Visual Studio 2012 C++ Compiler:

#include <string>

/**************************************/
class Base
{
public:
    int sum;
    virtual int Do() = 0;

    void Initialize()
    {
        Do();
    }
    Base()
    {
    }
};

/**************************************/
// Optionally declare class as "final" to avoid
// issues with further sub-derivations.
class Derived final : public Base
{
public:

    virtual int Do() override final
    {
        sum = 0 ? 1 : sum;
        return sum / 2 ; // .5 if not already set.
    }

    Derived(const std::string & test)
        : Derived() // Ensure "this" object is constructed.
    {
        Initialize(); // Call Pure Virtual Method.
    }
    Derived()
        : Base()
    {
        // Effectively Instantiating the Base Class.
        // Then Instantiating This.
        // The the target constructor completes.
    }
};




/********************************************************************/
int main(int args, char* argv[])
{
    Derived d;
    return 0;
}
like image 935
e.s. kohen Avatar asked Feb 04 '13 06:02

e.s. kohen


People also ask

What is delegate constructor?

Delegating constructors can call the target constructor to do the initialization. A delegating constructor can also be used as the target constructor of one or more delegating constructors. You can use this feature to make programs more readable and maintainable.

Can a constructor be declared as virtual?

Constructor can not be virtual, because when constructor of a class is executed there is no vtable in the memory, means no virtual pointer defined yet.

Can we override pure virtual function?

Yes !! It improves code clarity: override keyword prevents ambiguity and convey it's meaning of overriding its base class method.


2 Answers

With the updates, the example code looks okay to me, with the caveat that if you ever make a subclass of Derived, the subclass's override of Do() won't get called by Derived(const std::string &), rather Derived::Do() will still get called; which might not be what you wanted. In particular, when Initialize() is called from the Derived(const std::string &) constructor, the object is still "only" a Derived object and not a SubDerived object yet (because the SubDerived layer of construction-code hasn't started yet) and that is why Derived::Do() would be called and not SubDerived::Do().

Q: What if the subclass uses the same delegation pattern to ensure everything is instantiate in the same way?

A: That would mostly work, but only if it's okay for Derived::Do() to be called before SubDerived::Do() is called.

In particular, say you had class SubDerived that did the same things as Derived does above. Then when the calling code did this:

SubDerived foo("Hello");

the following sequence of calls would occur:

Base()
Derived()
Derived(const std::string &)
  Base::Initialize()
    Derived::Do()
SubDerived()
SubDerived(const std::string &)
  Base::Initialize()
    SubDerived::Do()

... so yes, SubDerived::Do() would eventually get called, but Derived::Do() would have been called also. Whether or not that will be a problem depends on what the various Do() methods actually do.

Some advice: Calling virtual methods from within a constructor is usually not the best way to go. You might want to consider simply requiring the calling code to call Do() manually on the object after the object is constructed. It's a bit more work for the calling code, but the advantage is that it you can avoid the not-very-obvious-or-convenient semantics that come into play when doing virtual method calls on partially-constructed objects.

like image 200
Jeremy Friesner Avatar answered Oct 10 '22 02:10

Jeremy Friesner


In a typical single-constructor inheritance scenario, it is UB to call a pure virtual function in the base constructor:

[C++11: 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.

struct Base
{
   Base()
   {
      foo();  // UB
   }

   virtual void foo() = 0;
};

struct Derived : Base
{
   virtual void foo() {}
};

There is no exemption here for such a call being made in a delegated constructor call, because at this point the more-derived part of the object still hasn't been constructed.

struct Base
{
   Base()
   {
      foo();  // still UB
   }

   Base(int) : Base() {};

   virtual void foo() = 0;
};

struct Derived : Base
{
   virtual void foo() {}
};

Here's the Wikipedia passage that you cited:

C++11 considers an object constructed once any constructor finishes execution. Since multiple constructors will be allowed to execute, this will mean that each delegate constructor will be executing on a fully constructed object of its own type. Derived class constructors will execute after all delegation in their base classes is complete.

The key is the second bolded sentence, rather than the first, as one may misconstrue from a quick glance.

However, your posted code snippet is fine, and that's because the derived constructor body is undergoing execution, not the constructor for the abstract class, which has already been completely constructed. That said, the fact that you've had to ask for proof that it's safe should be some indication that this is not the most expressive or intuitive approach, and I would try to avoid it in your design.

like image 27
Lightness Races in Orbit Avatar answered Oct 10 '22 02:10

Lightness Races in Orbit