Solved Correct way to manage class instantiation, destruction and reinstantiation
-
My application has two operating modes, which we can consider to be modeA and modeB for the purposes of this post. I've addressed some related issues/questions regarding switching between modes A and B in this post. The linked post pointed me in the right direction, but I'm having some issues regarding class/object destruction and reinstantiation.
My setup is as follows:
-
My main.cpp sets up the QQmlApplicationEngine, as well as a couple of class objects whose lifetime is that of the application. I.e. they are used in both modes A and B, but are not destroyed and reinstatiated when changing modes; they are instantiated once during setup of main.cpp.
-
The QQmlApplicationEngine, as well as the aforementioned 'persistent' class instances are then passed to a 'controller' class by pointer (currently using a QPointer, but I have some questions about this later).
-
The 'controller' class is responsible for instantiating the class objects required for the selected mode, and then finally loading the mode's respective QML file.
-
When the mode is changed (i.e. the user opts to go into modeA while in modeB or vice versa), the 'controller' class initiates the destruction of the current mode's class objects (note: I use signals/slots here to ensure any pre-destruction processing within the instantiated mode classes is handled correctly). Once all pre-destruction processing is complete, the 'controller' class deletes the mode's class objects.
-
At the moment, I then use a function in main.cpp to delete and reinstantiate the 'controller' class. In theory, the new 'controller' class instance now initialises the newly selected mode as if the application were just starting (i.e. a hard reset of the application as opposed to a runtime reset of the application).
Before I get into some example code, I'll list some of my observations thus far:
-
When the application is first launched, the process works as intended. The selection between modeA and modeB is stored in a configuration file, which is read in main.cpp and made available to the 'controller' class by way of one of the class pointers. The 'controller' class reads the configuration (modeA or modeB), and instantiates the respective class objects and loads the mode's QML file.
-
Once the selected mode is running, if I change to the other mode (by way of a simple QML button and a series of connections and signals), the process again works as intended (seemingly only most of the time, sometimes I will receive a Segmentation Fault on the first mode change attempt; see next point below for more regarding the Segmentation Fault). A signal is sent to the 'handler' class for the current mode (i.e. a class which manages the instantiation of the required objects, connections, etc. for that mode), where each of the instantiated class objects conducts any pre-destruction processing before being deleted. The 'controller' class then deletes the 'handler' object for the current mode, and is then itself deleted in main.cpp. A new 'controller' instances is then instantiated, and the 'controller' class successfully sets up the new mode and loads its QML file.
-
At this point, if I try and go back to the original mode, 90% of the time I'll receive a Segmentation Fault. By going through my qDebug() output, I can see that most of the process is followed, to the point that the 'handler' class is setup, with each of the mode's class objects instantiated and signal/slots configured (i.e. QObject::connect()), but at some point there is a Segmentation Fault.
I have no doubt that I'm missing something with the instantiation, destruction and reinstantiation of these class objects, as well as how I've used pointers to pass the QQmlApplicationEngine and 'persistent' class objects from main.cpp to the 'controller' class. Any guidance on where I'm going wrong would be greatly appreciated!
For the purposes of this post, and to make it as generic and reusable by others in the future, I've included only the bare essentials in the below code. For the same reason, I've used generic class, object, etc. naming as well.
main.cpp:
#include "path/to/headers" //Class Instances QPointer<myPersistentClass1> myPersistentClass1Instance; QPointer<myPersistentClass2> myPersistentClass2Instance; QPointer<myControllerClass> myControllerClassInstance; void reloadMyController(QQmlApplicationEngine *engine, QPointer<myPersistentClass1> myPersistentClass1Instance, QPointer<myPersistentClass2> myPersistentClass2Instance) { //Delete the existing myControllerClass1Instance object delete myControllerClassInstance; //Instantiate a new myControllerClass object myControllerClassInstance = new myControllerClass(); //Re-configure the signal/slot mechanism for reloading the class - not sure if this is needed? QObject::connect(myControllerClassInstance, &myControllerClass::reloadClass, [&engine]()->void{reloadMyController(&engine, myPersistentClass1Instance);}); //Initialise the controller class myControllerClassInstance->initialise(&engine, myPersistentClass1Instance, myPersistentClass2Instance); } int main(int arg, char *argv[]) { QApplication app(argc, argv); QQmlApplicationEngine engine; //Setup the persistent classes myPersistentClass1Instance = new myPersistentClass1(); myPersistentClass2Instance = new myPersistentClass2(); myControllerClassInstance = new myControllerClass(); //Setup the signal/slot mechanism for reloading the myControllerClass object QObject::connect(myControllerClassInstance, &myControllerClass::reloadClass, [&engine]()->void{reloadMyController(&engine, myPersistentClass1Instance, myPersistentClass2Instance);}); //Register some QML types etc. //Initialise the controller class myControllerClassInstance->initialise(&engine, myPersistentClass1Instance, myPersistentClass2Instance); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }
myControllerClass.h:
#include "path/to/modeA/headers" #include "path/to/modeB/headers" class myControllerClass : public QObject { Q_OBJECT //Mode QML URL Q_PROPERTY(QString modeQMLURL READ modeQMLURL NOTIFY modeQMLURLChanged) public: explicit myControllerClass(QObject *parent = nullptr); QString modeQMLURL(){return this->m_modeQMLURL;} signals: //Signal to main.cpp to reload the myControllerClass class void reloadClass(); //Signal to notify QML of updated Loader URL void modeQMLURLChanged(); //Signal to initiate the destruction of the current mode void destroyClass(); public slots: //Initialise the myControllerClass class from main.cpp void initialise(QQmlApplicationEngine*, QPointer<myPersistentClass1>, QPointer<myPersistentClass2>); //Slot to receive user input to change modes void changeMode(); //Slot to receive notification that the mode class object has been destroyed void classDestroyed(); public: QString m_modeQMLURL; //QQmlApplicationEngine pointer QQmlApplicationEngine *engine; //Class pointers - I'm not sure if this is correct given I'm receiving QPointers? myPersistentClass1 *myPersistentClass1Instance; myPersistentClass2 *myPersistentClass2Instance; //Pointers to the 'handler' classes for modes A and B modeAHandler *modeAHandlerInstance; modeBHandler *modeBHandlerInstance; private: //Set mode to modeA void setModeA(QPointer<myPersistentClass1>); /Set mode to modeB void setModeB(QPointer<myPersistentClass2>); };
myControllerClass.cpp:
#include "myControllerClass.h" myControllerClass::myControllerClass(QObject *parent) : QObject(parent) { //Constructor - empty } void myControllerClass::initialise(QQmlApplicationEngine *engineObj, QPointer<myPersistentClass1> myPersistentClass1Obj, QPointer<myPersistentClass2> myPersistentClass2Obj) { //Assign my class pointers - again, not sure if this is correct/the right approach engine = engineObj; myPersistentClass1Instance = myPersistentClass1Obj; myPersistentClass2Instance = myPersistentClass2Obj; //Read configuration file to determine whether to initialise and launch modeA or modeB (configuration not shown for simplicity) if (configuration.mode == "modeA") { setModeA(myPersistentClass1Instance); } else { setModeB(myPersistentClass1Instance); } //Load the master/top level QML file, in which the modeA/modeB QML file is loaded dynamically in a Loader //As the QQmlApplicationEngine is persistent, we only load it the first time, so a check to ensure it's empty is performed to prevent loading it again on subsequent initialisation of the myControllerClass class if (engine->rootObjects().isEmpty()) { engine->load(QUrl(QStringLiteral("qrc:/path/to/master.qml"))); } } void myControllerClass::changeMode() { //User input to change mode received; emit signal to initiate destruction of current mode emit destroyClass(); } void myControllerClass:classDestroyed() { //Signal received from previously instantiated class; class has been destroyed //Emit signal to main.cpp to reload the myControllerClass class emit reloadClass(); } void myControllerClass::setModeA(QPointer<myPersistentClass1> myPersistentClass1Obj) { //Register some meta types //Register some QML types //Instantiate the modeA handler and assign our class pointer modeAHandlerInstance = new modeAHandler(); //Initialise the modeAHandler() class modeAHandlerInstance->initialise(myPersistentClass1Obj); //Connect the myControllerClass destroyClass() signal with the modeAHandler destroyClass() slot QObject::connect(this, &myControllerClass::destroyClass, modeAHandlerInstance, &modeAHandler::destroyClass); //Connect the QObject::destroyed signal of modeAHandler with the myControllerClass classDestroyed slot QObject::connect(modeAHandlerInstance, &QObject::destroyed, this, &myControllerClass::classDestroyed); //Set the master.qml Loader URL to the modeA QML path m_modeQMLURL = "qrc:/path/to/modeA.qml"; //Emit modeQMLURL changed signal emit modeQMLURLChanged(); } void myControllerClass::setModeB(QPointer<myPersistentClass1> myPersistentClass1Obj) { //Not shown for simplicity; it has the same structure as setModeA above, just with different meta types, QML types, etc. }
I'll include below the pertinent parts of the modeAHandler class (I'll only show the source file for simplicity, but will add commentary where needed pertaining to its header file). As above, the modeBHandler structure is the same, just with different internal functions, types, etc.
modeAHandler.cpp:
#include "modeAHandler.h" modeAHandler::modeAHandler(QObject *parent) : QObject(parent) { //Constructor - empty } modeAHandler::~modeAHandler() { //Destructor - empty } void modeAHandler::destroyClass() { //Signal received from myControllerClass to destroy the class emit classDestructionSignal(); //NOTE: The above classDestructionSignal() is connected to each of the instantiated children classes within modeAHandler (shown in subsequent code below). The intent with this signal is to allow the children class the opportunity to execute any pre-destruction processing. As these children classes are not in the same thread as modeAHandler and contain both timer and socket types (e.g. QTimer and QTcpSocket), additional care is required before destroying them. } void modeAHandler::classDestructionReady(QString childClassInstance) { //NOTE: This slot receives a signal from each of the modeAHandler children classes when their pre-destruction processing has been complete. Destruction of the class children will not take place until all children classes have confirmed they're ready for destruction. //A boolean for each child class is set in the modeAHandler header file; only showing 2 child classes for simplicity if (childClassInstance == "childClass1Instance") { childClass1InstanceReady = true; } else if (childClassInstance == "childClass2Instance") { childClass2InstanceReady = true; } //Check to make sure all child classes are ready if (childClass1InstanceReady && childClass2InstanceReady) { //Delete each of the child class instances - these instances are setup as pointers in the header file, then assigned in the modeAHandler::initialise() function below delete childClass1Instance; delete childClass2Instance; } } void modeAHandler::classDestroyed(QString childClassInstance) { //NOTE: This slot receives a signal from each of the modeAHandler children classes when they have been destroyed. Destruction of the modeAHandler class will not take place until all children classes have confirmed they've been destroyed. //A boolean for each child class is set in the modeAHandler header file; only showing 2 child classes for simplicity if (childClassInstance == "childClass1Instance") { childClass1InstanceDestroyed = true; } else if (childClassInstance == "childClass2Instance") { childClass2InstanceDestroyed = true; } //Check to make sure all child classes have been destroyed if (childClass1InstanceDestroyed && childClass2InstanceDestroyed) { //Delete the modeAHandler class - I'm again unsure whether this is correct/the right way? delete this; } } void modeAHandler::initialise(QPointer<myPersistent1Class> myPersistentClass1Obj) { //Assign my class pointer (again defined in the header file) myPersistentClass1Instance = myPersistentClass1Obj; //Instantiate the modeAHandler children classes and assign our class pointers - again, only showing 2 for simplicity childClass1Instance = new childClass1(); childClass2Instance = new childClass2(); //Establish the children class destroyed() signals QObject::connect(childClass1Instance, &QObject::destroyed, [=](){this->classDestroyed("childClass1Instance");}); QObject::connect(childClass2Instance, &QObject::destroyed, [=](){this->classDestroyed("childClass2Instance");}); //Establish the children class destroyClass() slots QObject::connect(this, &modeAHandler::classDestructionSignal, childClass1Instance, &childClass1::destroyClass); QObject::connect(this, &modeAHandler::classDestructionSignal, childClass2Instance, &childClass2::destroyClass); //Move each of the child classes to their own thread //NOTE: Each of the thread objects below are again setup as QThread* pointers in the header file and then assigned as new QThreads below. childClass1Thread = new QThread; childClass2Thread = new QThread; childClass1Instance->moveToThread(childClass1Thread); childClass2Instance->moveToThread(childClass2Thread); //Establish some final thread management signals/slots QObject::connect(childClass1Instance, &QObject::destroyed, childClass1Thread, &QThread::quit); QObject::connect(childClass2Instance, &QObject::destroyed, childClass2Thread, &QThread::quit); QObject::connect(childClass1Thread, &QThread::finished, childClass1Thread, &QThread::deleteLater); QObject::connect(childClass2Thread, &QThread::finished, childClass2Thread, &QThread::deleteLater); //Other miscellaneous signal/slot setup and further processing //Start each of the child classes threads childClass1Thread->start(); childClass2Thread->start(); //NOTE: At this point, the child classes for modeA are running. }
I believe that should be sufficient code to illustrate my intent, and hopefully identify where I'm going wrong. As mentioned in the code above, I really don't know if my use of QPointers (and passing of pointers more generally) is correct. Given that I'm receiving a Segmentation Fault, I suspect probably not.
Any guidance, clarification or correction would be greatly appreciated as always!
-
-
void modeAHandler::destroyClass() { //Signal received from myControllerClass to destroy the class emit classDestructionSignal(); }
and
QObject::connect(myControllerClassInstance, &myControllerClass::reloadClass, [&engine]()->void{reloadMyController(&engine, myPersistentClass1Instance);});
Can't work together as the function is called directly. You need to use at least a Qt::QueuedConnection
-
@Christian-Ehrlicher Thank you very much for reviewing my post and providing this input, it's greatly appreciated.
It has obviously been a particularly long day, because I'm not fully understanding how the two snippets you've posted relate to each other? I've not used the lambda-style signal/slot before, is there a better way to implement that code that utilises a Queued Connection?
If the issue you've raised is causing a coherency issue, it'd certainly explain why sometimes the reload works and other times it doesn't.
-
I don't understand your problem - simply mark it as QueuedConnection so the emit does not directly call your lambda.
-
Thanks again @Christian-Ehrlicher, I honestly don't know if I would have spotted that error!
I've changed the line to the following:
QObject::connect(myControllerClassInstance, &myControllerClass:reloadClass, myControllerClassInstance, [&engine]()void->{reloadMyController(&engine, myPersistentClass1Instance);}, Qt::QueuedConnection);
and it now appears to be working! I've launched the application in modeA, and switched back and forth between mode A and B a dozen times without issue.
Thanks again!