Is There Any Automated Way to Implement Post-Constructor and Pre-Destructor Virtual Method Calls

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

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().

Cleanup resources of base class before destructing derived class

An alternative could be to not let TestRunner inherit from Runnable but to let Runnable have a TestRunner member variable. This avoids the problem completely.

I've removed the smart pointers here because I think they are only taking focus from the real problem that the derived class object may get destroyed under the feet of the thread. That is, before the virtual member functions gets called and also while the thread is running and working on member variables in the derived class.

#include <atomic>
#include <iostream>
#include <thread>
#include <type_traits>
#include <utility>

// A base class for TestRunner
class RunnerBase {
public:
void stop() { terminate_ = true; }
bool terminated() const { return terminate_; }

virtual void operator()() = 0;

private:
std::atomic<bool> terminate_ = false;
};

The Runnable constructor now forwards its arguments to the member variable (which is of type TestRunner in our case).

template<class T>
class Runnable final {
public:
static_assert(std::is_base_of_v<RunnerBase, T>, "T must inherit from RunnerBase");

template<class... Args> // constructor forwarding to the runner
Runnable(Args&&... args) : runner{std::forward<Args>(args)...} {}

void run() {
if(running_thread_.joinable()) return;
running_thread_ = std::thread(&Runnable::run_thread, this);
}

~Runnable() { stop(); }

void stop() {
if (running_thread_.joinable()) {
runner.stop();
running_thread_.join();
}
}

protected:
void run_thread() {
std::cout << "a" << std::endl;
runner();
std::cout << "b" << std::endl;
}

private:
std::thread running_thread_;
T runner;
};

TestRunner inherits from RunnerBase and can check terminated() if it's time to terminate.

#include <vector>

class TestRunner : public RunnerBase {
public:
TestRunner(size_t data_size) : foo(data_size) {}

void operator()() override {
while(!terminated()) {
// work on data that only exists in the derived class:
for(auto& v : foo) ++v;
std::cout << '.';
}
}

private:
// Some data that the thread works on that wóuld get destroyed before
// the base class destructor could call stop() if inheriting `Runnable`
std::vector<int> foo;
};

The creation is just slightly different.

int main() {
Runnable<TestRunner> testRunner(1024U);
testRunner.run();
}

Any reasons to use a a method instead of the class own destructor to clean up in C++?

The only advantage to moving initialization out of the constructor, and for removing cleanup out of the destructor is when you've got a base class framework where you want to reliably call virtual methods during these stages.

Since the vtable is changing during construction/destruction calls to virtual functions don't resolve to the most derived instance. By having explicit Initialize/Shutdown methods you can be sure the virtual functions dispatch correctly.

Please note, this isn't an answer that advocates this approach, just one that is trying to work out why they've suggested it!

OOP design: force calling virtual members from constructor

Well, you could fake it with on-demand/lazy construction:

class shape
{
public:
shape(std::shared_ptr<configuration> conf) {}

std::shared_ptr<generated> get_mesh()
{
if (mesh == nullptr) mesh = create_mesh();
return mesh;
}

protected:
// Should be implemented to create and return the appropriate mesh object
virtual std::shared_ptr<generated> create_mesh()() = 0;

private:
// don't access this directly, always call get_mesh() instead!
std::shared_ptr<generated> mesh;
};

The mesh won't be created until the first time someone calls get_mesh(), but as far as the outside world is concerned, the mesh will always be available when it is needed. (Note that this presumes that your shape class's own constructor doesn't need access to the mesh object; if it does, then John Zwinck's suggestion is probably your only option)

Is there an umbrella term for constructor/destructor methods?

I think the closest you are going to get is special member function but this also includes copy/move constructor and assignment , section 12 [special] says:

The default constructor (12.1), copy constructor and copy assignment operator (12.8), move constructor
and move assignment operator (12.8), and destructor (12.4) are special member functions [...]

Calling overriden class methods as a chain in C++

When you declare a virtual function it will call to the most-derived handler of the function, and then from there you have each handler call NextMostDerivedClass::preDestroy and the calls will go to the next-most-derived handler of the function, and you can again call NextMostDerivedClass::preDestroy, and so on. This is the same as the path a virtual destructor takes, except that you don't have to call anything manually with destructors, it's automated. If you were to put the cout statements from the below code sample into your destructors, you could see the same output as the sample below provides.

#include <iostream.h>

class Foo
{
public:
virtual void PreDestroy()
{
cout << "Foo preDestroy";
}
}

class Bar : public Foo
{
public:
void PreDestroy()
{
cout << "Bar preDestroy\n\n";

Foo::PreDestroy();
}
}

class MostDerived : public Bar
{
public:
void PreDestroy()
{
cout << "MostDerived preDestroy\n\n";

Bar::PreDestroy();
}
}

int main()
{
MostDerived testObj;

testObj.PreDestroy();
}

Output should be:

MostDerived preDestroy

Bar preDestroy

Foo preDestroy



Related Topics



Leave a reply



Submit