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

2

13

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;
}
Lynsey answered 4/2, 2013 at 6:18 Comment(15)
And Yes, the relevant quote in marked duplicate holds good and is unchanged in C++11. Also, saying According to the C++11 spec is misleading, it creates the illusion that the quote is from C++11 standard but it is not. And if it is you should present the unaltered content.Edvard
Didn't realise C++11 quotes WikipediaDelatorre
I did read that post beforehand. This is not a duplicate. This is in the context of C++ 11, not 03, and the question is about the dangers since the ones mentioned regarding 03 are moot since the objects /are/ constructed. Looking for C++ 11 spec reference.Lynsey
AFAICT (from examination of the code and also from compiling the code with 'clang++ -std=c++11' and running it) the above program will terminate in the Base class ctor with a "pure virtual method called" error, because during the Base ctor's execution the object is not a Derived object yet and thus the only implementation of Do() that is available is the pure-virtual stub. Am I missing some new C++11 trick here?Kingfish
Following Example Compiles in Nov CTP of Visual Studio 2012 C++ Compiler, Undefined Behaviors do not demand a diagnostic from the compiler. I do not have access to an compiler right now(typing through phone), but as Jeremy points out your code sample does not produce your expected results?Edvard
@JeremyFriesner I fixed the code. It should run now for you.Lynsey
Yes, it runs without crashing for me now (once I added a main() that instantiates a Derived object, of course)Kingfish
@JeremyFriesner Added a main. :) Okay, so now all that is out of the way... Do you see any big risks with this, as long as I make the constructor private so people don't inadvertently call it out of sequence? In this case, Derived().Lynsey
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.Kingfish
@JeremyFriesner That is exactly what I am looking for.. Could you post that as an answer, but clarify /why/ the subclass' override won't be invoked? What if the subclass uses the same delegation pattern to ensure everything is instantiated in the same way?Lynsey
That is not a duplicate. This question addresses C++11 delegate constructors specifically. That they turn out not to be relevant to the safety of making this particular function call does not change that!Delatorre
@LightnessRacesinOrbit: I marked the Q as duplicate when the Q was different from what it is now. There was no sample code and there was just a dodgy illusion of C++11 says so. The Q has been reformed since then and yes it is much more clear now and is not a duplicate.Edvard
@AlokSave: From the first revision: Are there any dangers in invoking Pure Virtual functions in a base class after the first constructor delegation has been completed? :PDelatorre
As I said there was only a purported claim about what the C++11 standard says in this regards with little proof, for me it didn't qualify as a worthy proof enough.Edvard
@AlokSave: I agree. However we are talking about the content/direction/meaning of the question, not about the OP's ideas about what the answer will be.Delatorre
K
8

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.

Kingfish answered 4/2, 2013 at 7:18 Comment(5)
In the context of instantiating objects via an abstract factory, I can not always perform a second initialization step, (unless of course the instantiated instance implements an Initialize : CommandPattern behavior of some sort). In the end, I can throw "final" around to solve that particular problem. But, if in SubDerived, I delegate the constructor to Base, and never Derived, would I still have the same issue? I modified the code to see if the issue is eliminated.Lynsey
Constructor delegation can't help you with this problem; it will always be the case that during a superclass's constructor's execution, all methods and member variables defined in any subclasses will be unavailable. There's really no way around it. To understand why, imagine if C++ worked the way you want it to, and a call to Initialize() made from within the Derived ctor did actually call SubDerived::Do(). SubDerived::Do() would likely try to access a member variable from the SubDerived subclass, but -- oops! member variables declared for SubDerived haven't been constructed yet! -> Crash!Kingfish
Btw it's not clear why a factory class couldn't call Do() on the constructed object before returning the constructed object back to its caller. My factory classes commonly do that sort of thing without any issue.Kingfish
Oops, I misread your question in your comment above; ignore my first response. I'll try again: If SubDerived ctor delegated to Base, then Derived ctor would never run and thus never call Initialize(), so that would avoid the call to Derived::Do(). OTOH it would also avoid any other work that the Derived ctor would have done, which might be a problem (depending on what else was in the Derived ctor).Kingfish
Alright, this is definitely the/an answer about risks... Will mark as answer. Thank you again!Lynsey
D
5

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.

Delatorre answered 4/2, 2013 at 7:17 Comment(5)
I was asking because in part because there is a lot of hesitancy based on old practices, and in part to document a way to do it in C++ 11. The key, I think are the two statements: undefined behavior if call calling a pure virtual function directly or indirectly for the an BEING CREATED, which is not the case if at least one of the delegated constructors has finished in the class implementing the pure virtual method. That override should be marked final though, or the derived class.Lynsey
There is an exemption here if any of the delegated constructors of the class implementing the pure virtual method have completed, leaving the object in a "constructed" state. But, what are the risks?Lynsey
@WindAndFlame: If the call is found in a derived constructor, sure. Please read my whole answer, particularly the final paragraph.Delatorre
Exactly, we are on the same page. But, if you delegate in the derived object, to that same derived object, construction DOES finish early, and the virtual methods can be invoked.Lynsey
@WindAndFlame: Yes, which is what my final paragraph says.Delatorre

© 2022 - 2024 — McMap. All rights reserved.