I know that before the superclass ctor is finished, the subclass (and vtable) is not yet complete. But, I have a situation where, logically, I must be able to change the initialization of the superclass, and I'm trying to figure out how I can do that.
Here is a simplified example:
class Window {
std::thread uiThread;
void uiThreadProc(){
try {
UIInit();
NotifyOtherThreadThatUIInitFinished();
} catch(...){
UIClean();
throw; // actually using an exception_ptr but it's irrelevant here
}
EventLoop();
UICleanup();
}
virtual void UIInit();
virtual void UICleanup();
Super(){
CreateUIThread();
WaitForUIThreadToFinishUIInit();
}
~Super(){
SendQuitEventToUIThread();
WaitForUIThreadToFinish();
}
}
class Overlay : public Window {
Overlay(){
// can't do stuff before UIInit because Window ctor returns when UIInit finishes
}
void UIInit() override {
// won't be called because UIInit is called before Window ctor returns
}
void UIClean() override {
// won't get called if UIInit throws
}
~Overlay(){}
}
Currently, I'm trying to make it work by making the ctors private and moving the logic to an Init method that gets called after ctors, like this:
class Window {
static std::shared_ptr<Window> Create(){
auto window = std::make_shared<Window>();
window->Init();
return window;
}
virtual void Init() { /* actual ctor content here */ }
virtual void Cleanup() { /* actual dtor content here */}
bool cleanupCalled = false;
~Window(){
if(!cleanupCalled){
cleanupCalled = true; // with mutex and locks...
Cleanup();
}
}
}
class Overlay : public Window {
// same as above, but now overriding Init() and Cleanup()...
}
I assume this would work, but it feels super hacky and convoluted.
And I don't understand why this is the case from a design perspective, why don't ctors first create the complete vtable, then call the hierarchy of ctors? The vtable doesn't depend on member initialization, so it won't break things to do it first, correct?
Is this not a good use for inheritance?
I need to be able to override UIInit
because some things must run on the UI thread. I could send it as functions/events and the EventLoop would execute that, but that would seriously break the logic of "when ctor finishes, the object is fully initialized", or if it absolutely has to run before the EventLoop. I could make my own thread event handling class, but that simply seems wrong for such a trivial case.
Any architectural suggestions are very welcome.