Force derived ctor or dtor to do something

Started by
12 comments, last by SiCrane 14 years, 11 months ago
Assume a case where all object access will be done through a pointer of the base class. While for non-ctor/dtor functions one can have certain behavior enforced by doing this: class Base { public: ... Movezig(...) { ...; MovezigEx(...); ...; } protected: virtual ... MovezigEx(...) { }; Then base-enforced functionality will be always executed since the client can't call MovezigEx() directly. However, how do I get this effect for ctors/dtors, when a virtual function call from a ctor/dtor is not really virtual, it always calls the base class version? For example, I need to enforce that all derived classes have ctor and dtor always do this: Derived::Derived(someFlag, ...) { if (someFlag) Doit(...); } Derived::~Derived(someFlag, ...) { if (otherFlag) Doelse(...); } where Doit() and Doelse() are pure virtual in the base class. I cannot implement the logic in the base ctor (dtor) because Doit() (Doelse()) is part Derived which at the time of calling of ctor (dtor) hasn't been created yet (has been destroyed already).
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
Advertisement
Well obviously, destructors don't take parameters, you so can't do that anyway.

I would suggest you create an "Initialize" and "Destroy" function or something like that and do it via that.
I think that what it makes sense to do depends on what you're really trying to do.
You need to show what the real code is trying to do rather than just some mock example where you're trying to figure out how to do something the wrong way.
Even a FinalConstruct / FinalRelease mechanism shouldn't take parameters.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
In C++, never call virtual functions during construction or destruction.

What you want sounds a little strange. You want different behavior, but you're trying to use the base class for it? That's what the derived classes are for, not the base class. You'll probably want to rethink where you put that specific piece of functionality.
Create-ivity - a game development blog Mouseover for more information.
Tell me why you think you need it to behave this way. Go on; be detailed. Show a little actual code, even.
Oops, I made a mistake in the dtor example, otherFlag is an internal state variable.

I need such functionality because every derived class will use it, and I want to avoid code duplication, among other things.

I have two cases where I need such functionality. One is in dynamically loaded modules with two stages of initialization/clean-up because they are not often fully unloaded (however they must be partially unloaded frequently to clear VRAM, as a bunch of them are run in sequence each for a short time and only occasionally need to be fully reloaded when a new one is uploaded, or if they don't all fit into RAM). Moreover, I want the calling of a destructor to call the first-stage clean-up if the user has forgotten.

dtor example:

Module::~Module(void) // Fully unloading
{
if (_status) == RUNNING) Stop(); // Signal update threads to terminate
if (_status) Clear(); // User forgot to call first-stage unloading
}

This logic is in every derived class. Why should I have to write it time and again? It's a maintenance nightmare, especially since mostly other people will be writing a large varieties of other modules. I should be able to implement it and make any changes to it in a single location, yet I can't put it in base because Clear() is virtual.

While Stop() I can for the time implemented in base, Clear() is virtual in base so I cannot implement this behavior there.

In the other case, modules use classes where I need the constructor to do either full or partial initialization based on a parameter.

GLResource::GLResource(GLManager &gm, ...)
{
if (gm) gm.Register(this); // gm will do loading/unloading from/to VRAM possibly multiple times during GLResource lifetime
else Init(...); // User wants immediate initialization
}

But I can't do this since Init() is pure virtual. So I end up having to duplicate this code in all derived GL classes like texture, VBO, FBO, shader, etc.
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
Quote:Original post by Prune
Oops, I made a mistake in the dtor example, otherFlag is an internal state variable.

I need such functionality because every derived class will use it, and I want to avoid code duplication, among other things.


1) What is the functionality?
2) Why must it run during the constructor?
3) Why does it require members of the derived class?
4) If it doesn't require members of the derived class, then the only reason it could need to be performed virtual-ly is because it's done differently in each derived class. But in that case, how do you know that every derived class has to do it?
5) If it does require members of the derived class, then every derived class must provide the members in question. In which case, why not refactor by promoting them to the base?

There is no problem with the destructor behaving virtually. However, you must declare the base class destructor as virtual - even if it doesn't do anything. And if the derived class needs to do its own work in addition, then you will have to arrange for that to work out.

One common approach is the template-and-hook pattern: make a virtual member function cleanup(), and call it from the base destructor, after performing the common destruction work.

Quote:
GLResource::GLResource(GLManager &gm, ...)
{
if (gm) gm.Register(this); // gm will do loading/unloading from/to VRAM possibly multiple times during GLResource lifetime
else Init(...); // User wants immediate initialization
}


3 things worry me about this. The first is that you have a class named "Manager". The second is that you have auto-registering objects. And the third is that you are apparently able to convert a Manager instance to bool (or are you actually passing it by pointer?).

Quote:But I can't do this since Init() is pure virtual.


So, Init() does something different for each kind of resource, right? So you aren't really gaining anything by having it, since you aren't going to call Init() later anyway, right? (Why would I explicitly Init() an object when I could use a proper constructor?)
Quote:Original post by Zahlman
There is no problem with the destructor behaving virtually. However, you must declare the base class destructor as virtual - even if it doesn't do anything.

In the code I wrote, Module is a base class. I need to enforce the constraint that every destruction proceeds by first calling Stop() (base member), then Clear() (derived member as there will be different things to delete etc.).
Stop() must always run first and block until completion, otherwise the update threads will try to access memory Clear() frees.
But I am unable to call derived Clear() from base dtor (see below).
On the other hand, implementing this logic in every one of many derived modules, even if it's just two lines, is still code duplication--and one across separate dll for each module at that.

Quote:make a virtual member function cleanup(), and call it from the base destructor, after performing the common destruction work.

That's what I tried to do, but it doesn't work in my situation where the derived modules are compiled to separate binaries--dlls. The linker doesn't complain when a pure virtual Clear() is called by the main program as in say module->Clear(), where module is of type Module * (the dlls only export one function each, as in Module *GetModule(...) { return new SomeDerivedModule(...); } and the application never needs to see the declarations of various and upcoming derived module classes).
Yet when I try to call Clear() in ~Module() when building the main program, I get a linker error that Clear() is missing--as expected, since by the time the base destructor runs, the derived parts of the object have been destroyed and calling a derived function is impossible. So how do I get around this?

Quote:The first is that you have a class named "Manager". The second is that you have auto-registering objects. And the third is that you are apparently able to convert a Manager instance to bool (or are you actually passing it by pointer?).

Regarding the third one, I made a typo--it's a pointer. I just leave it to the user to specify avoidance of the manager.

Regarding the second one, that's why I have "if (mgr)", so the user can opt out by passing a null pointer.

Regarding the third one, the manager is nothing worrisome. I primarily use it to call Clear() on all GLResource derived objects when I call the first-stage cleanup function of a Module, where I need VRAM cleared (while not destroying the module as I want it to remain in RAM). Without it, the user would have to remember to manually call Clear() on all GLResource derived objects in a module every time he adds a new one--already tried it that way and it's a nightmare as modules are fairly complicated. I cannot use RAII to automate the process due to the need for these objects to be initialized and cleared multiple times during their lifetime. The "if (mgr) {...}" gives the user a way to use cases where he can avoid using the manager and have Init() and Clear() called from ctor/dtor, such as when say using temporary GLResource objects during an initialization phase.

Quote:So, Init() does something different for each kind of resource, right? So you aren't really gaining anything by having it, since you aren't going to call Init() later anyway, right? (Why would I explicitly Init() an object when I could use a proper constructor?)

Typical module lifetime:

ctor() { // Load from HD and precomputations; slow } ---> When scheduled time, Init(...) { // Create VRAM-resident stuff by calling Init() on GLResource-derived member objects } ---> Update() and Draw() in various threads ---> Clear() { // Stop() then remove VRAM-resident stuff by calling Clear() on GLResource-derived member objects } ---> Init() and run next scheduled module or ctor it first if it hasn't been yet ctored ---> ... other modules ... ---> Init() first module again ---> ...cycle keeps going, sometimes new modules uploaded by network and dynamically loaded; if RAM low or module removed from schedule: run its dtor() { // if not stopped, Stop(); if not cleared, Clear(); }

[Edited by - Prune on May 18, 2009 8:35:31 PM]
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
So any suggestions? The only way I can come up with is to use a wrapper around the base class Module, but that's a lot of extra to achieve what should be a simple effect.
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
Make the base class destructor protected, create a static member function in the base class that accepts a base pointer as an argument that calls your stop function then deletes the pointer and then use that static member function as a deleter. Then restrict object creation to a factory and have the factory after creation call the init function. For full effect have the factory return a shared_ptr where the static deleter is registered with the new object.

This topic is closed to new replies.

Advertisement