Is there any automated way to implement post-constructor and pre-destructor virtual method calls?
Asked Answered
P

10

20

Due to the well-known issues with calling virtual methods from inside constructors and destructors, I commonly end up with classes that need a final-setup method to be called just after their constructor, and a pre-teardown method to be called just before their destructor, like this:

MyObject * obj = new MyObject;
obj->Initialize();   // virtual method call, required after ctor for (obj) to run properly
[...]
obj->AboutToDelete();  // virtual method call, required before dtor for (obj) to clean up properly
delete obj;

This works, but it carries with it the risk that the caller will forget to call either or both of those methods at the appropriate times.

So the question is: Is there any way in C++ to get those methods to be called automatically, so the caller doesn't have to remember to do call them? (I'm guessing there isn't, but I thought I'd ask anyway just in case there is some clever way to do it)

Peculiarize answered 20/7, 2009 at 5:7 Comment(6)
What problem do you have with destructors?Woebegone
Maybe you should describe your actual problem, maybe you don't actually need these calls...Woebegone
if you "commonly" need to call virtual methods from ctors or dtors, it sounds like you have a major design problem. Can you give an example of a class where this is necessary? Most likely, there's a simpler solution. (As usual, I'd expect RAII to solve the problem. Delegate the problem to on or more member variables, with their own ctors/dtors each doing their own part of initialization/teardown.Schiff
Example: I have a Thread class that is used to manage a thread that it holds internally. The user subclasses the Thread class to supply his own entry-point method and member variables for the thread to use. Currently, the user must make sure to call ShutdownInternalThread() before deleting the thread object, otherwise there is a race condition between the time when the subclass's destructor is called and when the Thread class's destructor is called, during which the thread may try to access subclass member variables that were already destroyed. I'd like to remove ShutdownInternalThread().Peculiarize
Example: I have a Window class with a virtual method GetWindowTypeName() that the subclass must implement to return the name of the window. I'd like to ensure that setWindowTitle(GetWindowTypeName()) gets called to set the window title appropriately in Qt-land, but I can't do that in the Window class ctor since virtual method calls won't work there. So it needs to happen in a separate method call later on; but I don't want to force the user to remember to make that separate call. (Note: this example is slightly contrived; since in Qt I can override showEvent()... but you get the idea)Peculiarize
A reasonable request. Delphi has had this for years. drbob42.com/delphi4/d4constr.htmPalomo
B
0

The main problem with adding post-constructors to C++ is that nobody has yet established how to deal with post-post-constructors, post-post-post-constructors, etc.

The underlying theory is that objects have invariants. This invariant is established by the constructor. Once it has been established, methods of that class can be called. With the introduction of designs that would require post-constructors, you are introducing situations in which class invariants do not become established once the constructor has run. Therefore, it would be equally unsafe to allow calls to virtual functions from post-constructors, and you immediately lose the one apparent benefit they seemed to have.

As your example shows (probably without you realizing), they're not needed:

MyObject * obj = new MyObject;
obj->Initialize();   // virtual method call, required after ctor for (obj) to run properly

obj->AboutToDelete();  // virtual method call, required before dtor for (obj) to clean up properly
delete obj;

Let's show why these methods are not needed. These two calls can invoke virtual functions from MyObject or one of its bases. However, MyObject::MyObject() can safely call those functions too. There is nothing that happens after MyObject::MyObject() returns which would make obj->Initialize() safe. So either obj->Initialize() is wrong or its call can be moved to MyObject::MyObject(). The same logic applies in reverse to obj->AboutToDelete(). The most derived destructor will run first and it can still call all virtual functions, including AboutToDelete().

Betray answered 20/7, 2009 at 9:51 Comment(8)
Except when Initialize() is reimplemented in a subclass of MyObject, and I need to call the subclass's implementation, not MyObject::Initialize(). Called from the MyObject constructor, it does not do what I need it to do. (AboutToDelete() has the same problem when called from MyObject::~MyObject()) Anyway, the "thing that happens after MyObject::MyObject() returns" is the execution of the subclass constructors... those need to happen before Initialize() runs. The logic is reversed for AboutToDelete(), which needs to run before any subclass destructors run.Peculiarize
That's obviously not the case here since new MyObject directly precedes the call. And your counter-example merely changes names. The most-derived constructor runs last, when all invariants have been established and all virtual functions can be called. That ctor can still call Initialize()Betray
The most-derived constructor can't safely call Initialize(), because it can't know for sure that it is the most-derived constructor. It very well could be that another class has subclassed it, and in that case Initialize() would be called too soon.Peculiarize
That's why each class in such cases offers a protected ctor that doesn't call Initialize().Betray
@JeremyFriesner Initialise should not be virtual.Lavona
-1: "Let's show why these methods are not needed": I think you are not thinking enough about class design. Having possibility to call virtual methods during destructors and constructors could enforce some useful behaviors. For example, I have a base VideoPlayer class that would like to call Stop on the destructor. With C++ I can't because there may stil be threads calling virtual methods. With C# this is perfectly safe because construction and destruction works completely differently and doesn't validate/invalidate parts of the object like C++ specification mandates.Faucal
@ceztko: A DerivedVideoPlayer no longer exists when DerivedVideoPlayer::~DerivedVideoPlayer has completed and VideoPlayer::~VideoPlayer starts executing. So anything that's needed to stop a DerivedVideoPlayer thread must complete before DerivedVideoPlayer::~DerivedVideoPlayer returns. You can't call DerivedVideoPlayer::Stop from VideoPlayer::~VideoPlayer because the DerivedVideoPlayer members no longer exist!. It is the time-mirrored variant of Initialise where you can't use members which do not exist yet.Betray
@Betray I know the problem with members no longer existing after destruction but I contested the affirmation that such pre-post destructors hooks are "not needed", as you seemed to imply, and offered you an use case that works in other languages where the semantics for destruction is different than C++. Anyway sorry for declaring the negative vote: it was an old comment and because now such declarations are filtered by the system, I would have not written it that way today.Faucal
H
11

While there is no automated way, you could force the users hand by denying users access to the destructor on that type and declaring a special delete method. In this method you could do the virtual calls you'd like. Creation can take a similar approach which a static factory method.

class MyObject {
  ...
public:
  static MyObject* Create() { 
    MyObject* pObject = new MyObject();
    pObject->Initialize();
    return pObject;
  }
  Delete() {
    this->AboutToDelete();
    delete this;
  }
private:
  MyObject() { ... }
  virtual ~MyObject() { ... }
};

Now it is not possible to call "delete obj;" unless the call site has access to MyObject private members.

Holster answered 20/7, 2009 at 5:11 Comment(3)
Destructor can (and should) be virtual, why go through the extra work?Charlenacharlene
This example is not so correct, as MyObjects could not be stored (easily) in standard containers (vector, list, etc.) And instead of Delete() method, override the delete operator for the class.Cloudless
If you intend MyObject to be derivable (as I suspect from declaring the destructor virtual) constructor and destructor should be declared protected rather than privateEcumenicist
D
4

The best I can think of is for you to implement your own smart pointer with a static Create method that news up an instance and calls Initialize, and in its destructor calls AboutToDelete and then delete.

Dishabille answered 20/7, 2009 at 5:10 Comment(0)
D
2

I used a very carefully designed Create() factory method (static member of each class) to call a constructor and initializer pair in the same order as C# initializes types. It returned a shared_ptr to an instance of the type, guaranteeing a heap allocation. It proved reliable and consistent over time.

The trick: I generated my C++ class declarations from XML...

Dalt answered 20/7, 2009 at 5:10 Comment(1)
I'm assuming you provided a custom deleter to shared_ptr, that included the call to the pre-destruction logic?Propellant
H
2

Except for JavedPar's idea for the pre-destruction method, there is no pre-made solution to easily do two-phase construction/destruction in C++. The most obvious way to do this is to follow the Most Common Answer To Problems In C++: "Add another layer of indirection." You can wrap objects of this class hierarchy within another object. That object's constructors/destructor could then call these methods. Look into Couplien's letter-envelop idiom, for example, or use the smart pointer approach already suggested.

Howlan answered 20/7, 2009 at 7:48 Comment(0)
A
2

http://www.research.att.com/~bs/wrapper.pdf This paper from Stroustrup will solve your problem.

I tested this under VS 2008 and on UBUNTU against g++ compiler. It worked fine.

#include <iostream>

using namespace std;

template<class T>

class Wrap
{
    typedef int (T::*Method)();
    T* p;
    Method _m;
public:
    Wrap(T*pp, Method m): p(pp), _m(m)  { (p->*_m)(); }
    ~Wrap() { delete p; }
};

class X
{
public:
    typedef int (*Method)();
    virtual int suffix()
    {
        cout << "X::suffix\n";
        return 1;
    }

    virtual void prefix()
    {
        cout << "X::prefix\n"; 
    }

    X() {  cout << "X created\n"; }

    virtual ~X() { prefix(); cout << "X destroyed\n"; }

};

class Y : public X
{
public:
    Y() : X() { cout << "Y created\n"; }
    ~Y() { prefix(); cout << "Y destroyed\n"; }
    void prefix()
    {
        cout << "Y::prefix\n"; 
    }

    int suffix()
    {
        cout << "Y::suffix\n";
        return  1;
    }
};

int main()
{
    Wrap<X> xx(new X, &X::suffix);
    Wrap<X>yy(new Y, &X::suffix);
}
Ate answered 20/7, 2009 at 9:35 Comment(1)
+1 Very interesting article. However This seems to only wrap standard methods not constructors and destructors.Wei
C
2

You can use static function template in the class. With private ctor/dtor. Run on vs2015 community

class A {
    protected:
    A() {}
        virtual ~A() {}
        virtual void onNew() = 0;
        virtual void onDelete() = 0;
    public:

        void destroy() {
            onDelete();
            delete this;
        }

        template <class T> static T* create() {
            static_assert(std::is_base_of<A, T>::value, "T must be a descendant of A");
            T* t = new T();
            t->onNew();
            return t;
        }
   };

class B: public A {
     friend A;

     protected:
          B() {}
          virtual ~B() {}

          virtual void onNew() override {
          }

          virtual void onDelete() override {
          }
};

int main() {
    B* b;
    b = A::create<B>();
    b->destroy();
}
Cessation answered 4/1, 2017 at 9:54 Comment(0)
H
1

I was stuck with the same problem, and after a bit of research, I believe there is not any standard solution.

The suggestions that I liked most are the ones provided in the Aleksandrescu et al. book "C++ coding standards" in the item 49.

Quoting them (fair use), you have several options:

  1. Just document it that you need a second method, as you did.
  2. Have another internal state (a boolean) that flags if post-construction has taken place
  3. Use virtual class semantics, in the sense that the constructor of the most-derived class decides which base class to use
  4. Use a factory function.

See his book for details.

Horwitz answered 5/6, 2012 at 8:28 Comment(0)
B
0

The main problem with adding post-constructors to C++ is that nobody has yet established how to deal with post-post-constructors, post-post-post-constructors, etc.

The underlying theory is that objects have invariants. This invariant is established by the constructor. Once it has been established, methods of that class can be called. With the introduction of designs that would require post-constructors, you are introducing situations in which class invariants do not become established once the constructor has run. Therefore, it would be equally unsafe to allow calls to virtual functions from post-constructors, and you immediately lose the one apparent benefit they seemed to have.

As your example shows (probably without you realizing), they're not needed:

MyObject * obj = new MyObject;
obj->Initialize();   // virtual method call, required after ctor for (obj) to run properly

obj->AboutToDelete();  // virtual method call, required before dtor for (obj) to clean up properly
delete obj;

Let's show why these methods are not needed. These two calls can invoke virtual functions from MyObject or one of its bases. However, MyObject::MyObject() can safely call those functions too. There is nothing that happens after MyObject::MyObject() returns which would make obj->Initialize() safe. So either obj->Initialize() is wrong or its call can be moved to MyObject::MyObject(). The same logic applies in reverse to obj->AboutToDelete(). The most derived destructor will run first and it can still call all virtual functions, including AboutToDelete().

Betray answered 20/7, 2009 at 9:51 Comment(8)
Except when Initialize() is reimplemented in a subclass of MyObject, and I need to call the subclass's implementation, not MyObject::Initialize(). Called from the MyObject constructor, it does not do what I need it to do. (AboutToDelete() has the same problem when called from MyObject::~MyObject()) Anyway, the "thing that happens after MyObject::MyObject() returns" is the execution of the subclass constructors... those need to happen before Initialize() runs. The logic is reversed for AboutToDelete(), which needs to run before any subclass destructors run.Peculiarize
That's obviously not the case here since new MyObject directly precedes the call. And your counter-example merely changes names. The most-derived constructor runs last, when all invariants have been established and all virtual functions can be called. That ctor can still call Initialize()Betray
The most-derived constructor can't safely call Initialize(), because it can't know for sure that it is the most-derived constructor. It very well could be that another class has subclassed it, and in that case Initialize() would be called too soon.Peculiarize
That's why each class in such cases offers a protected ctor that doesn't call Initialize().Betray
@JeremyFriesner Initialise should not be virtual.Lavona
-1: "Let's show why these methods are not needed": I think you are not thinking enough about class design. Having possibility to call virtual methods during destructors and constructors could enforce some useful behaviors. For example, I have a base VideoPlayer class that would like to call Stop on the destructor. With C++ I can't because there may stil be threads calling virtual methods. With C# this is perfectly safe because construction and destruction works completely differently and doesn't validate/invalidate parts of the object like C++ specification mandates.Faucal
@ceztko: A DerivedVideoPlayer no longer exists when DerivedVideoPlayer::~DerivedVideoPlayer has completed and VideoPlayer::~VideoPlayer starts executing. So anything that's needed to stop a DerivedVideoPlayer thread must complete before DerivedVideoPlayer::~DerivedVideoPlayer returns. You can't call DerivedVideoPlayer::Stop from VideoPlayer::~VideoPlayer because the DerivedVideoPlayer members no longer exist!. It is the time-mirrored variant of Initialise where you can't use members which do not exist yet.Betray
@Betray I know the problem with members no longer existing after destruction but I contested the affirmation that such pre-post destructors hooks are "not needed", as you seemed to imply, and offered you an use case that works in other languages where the semantics for destruction is different than C++. Anyway sorry for declaring the negative vote: it was an old comment and because now such declarations are filtered by the system, I would have not written it that way today.Faucal
I
0

I had the same problem for construction. This is my solution using C++14.

The idea is to declare an instance of the class Call in the same (or quite close) scope than the declaration of the final object, letting the destructor call the post-creation script.

# include <iostream>
# include <cassert>
# include <memory>
# include <typeinfo>

class A;

// This non-template class stores an access to the instance
// on which a procedure must be called after construction
// The functions are defined after A in order to avoid a loop
class Call
{
    protected:
        A* a;
    public:
                       Call();
        virtual        ~Call();
        virtual void   set(A& a_) = 0;
};

// In this class, the Source must be the final type created
template <typename Source>
class Call_ : public Call
{
    static_assert(std::is_final<Source>::value, "");
    public:
        Call_() : Call() {}
        virtual ~Call_() { assert(typeid(*this->a) == typeid(Source)); }
        virtual void set(A& a_) { this->a = &a_; }
};

class A
{
    protected:
        A(Call& call) { std::cout << "Build A" << std::endl; call.set(*this); } // <----
    public:
        A(A const&) { std::cout << "Copy A" << std::endl; }
        virtual ~A() { std::cout << "Delete A" << std::endl; }
        virtual void actions_after_construction() = 0; // post-creation procedure
};

Call::Call() : a(nullptr)
{}

Call::~Call()
{
    assert(this->a);
    this->a->actions_after_construction();
}

class B : public A
{
    protected:
        B(Call& call) : A(call) { std::cout << "Build B" << std::endl; }
    public:
        B(B const& b) : A(b) { std::cout << "Copy B" << std::endl; }
        virtual ~B() { std::cout << "Delete B" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by B" << std::endl; }
};

class C final : public B
{
    private:
        C(Call& call) : B(call) { std::cout << "Build C" << std::endl; }
    public:
        C(std::shared_ptr<Call> p_call = std::shared_ptr<Call>(new Call_<C>)) : C(*p_call) {}
        C(C const& c) : B(c) { std::cout << "Copy C" << std::endl; }
        virtual ~C() { std::cout << "Delete C" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by C" << std::endl; }
};

class D final : public B
{
    private:
        D(Call& call) : B(call) { std::cout << "Build D" << std::endl; }
    public:
        D(std::shared_ptr<Call> p_call = std::shared_ptr<Call>(new Call_<D>)) : D(*p_call) {}
        D(D const& d) : B(d) { std::cout << "Copy D" << std::endl; }
        virtual ~D() { std::cout << "Delete D" << std::endl; }
        virtual void actions_after_construction() { std::cout << "actions by D" << std::endl; }
};

int main()
{
    { C c; }
    { D d; }
    return 0;
}
Inquire answered 23/3, 2022 at 15:54 Comment(0)
B
-1

Haven't seen the answer yet, but base classes are only one way to add code in a class hierarchy. You can also create classes designed to be added to the other side of the hierarchy:

template<typename Base> 
class Derived : public Base {
    // You'd need C++0x to solve the forwarding problem correctly.
    Derived() : Base() {
        Initialize();
    }
    template<typename T>
    Derived(T const& t): Base(t) {
        Initialize();
    }
    //etc
private:
    Initialize();
};
Betray answered 21/7, 2009 at 7:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.