4

Good Morning everyone,

I am using QSharedPointer with my classes derived from QObject. Since they use the signal/slot mechanism, I must use the QObject::deleteLater() in order to properly destroy them, see for example:

~QObject() : "Deleting a QObject while pending events are waiting to be delivered can cause a crash. You must not delete the QObject directly if it exists in a different thread than the one currently executing. Use deleteLater() instead, which will cause the event loop to delete the object after all pending events have been delivered to it."

QSharedPointer and QObject::deleteLater

QSharedPointer(X *ptr, Deleter d): "The deleter parameter d specifies the custom deleter for this object. The custom deleter is called, instead of the operator delete(), when the strong reference count drops to 0. This is useful, for instance, for calling deleteLater() on a QObject instead"

Also pls notice that in the previous link it is written

"Note that the custom deleter function will be called with a pointer to type X, even if the QSharedPointer template parameter T is not the same.",

but this is a quite different behaviour respect to the constructor QSharedPointer(X *ptr) that says:

"Since Qt 5.8, when the last reference to this QSharedPointer gets destroyed, ptr will be deleted by calling X's destructor (even if X is not the same as QSharedPointer's template parameter T). Previously, the destructor for T was called." - (I am using Qt 5.7, so I expect the ~T destructor)

Well, at the end what I want to achieve is to call the proper destructor (of a child class) using the QSharedPointer, but since it is a QObject I need to use QObject::deleteLater(), but in my tests I am not able to achieve my goal.

I post a simple test and the result I had.

Can you pls tell me if I am doing something wrong?

It is correct what I expect from the test?

I am particularly interested in the case labeled with "INTERESTING CASE"

class A : public QObject
{
public:
    A() : QObject() {};
    virtual ~A() { qDebug() << "Destructor of A"; }
};

class B : public A
{
public:
    B() : A() {}
    ~B() { qDebug() << "Destructor of B"; }
};

int main(int argc, char*argv[])
{
    qDebug() << "QT version " << QT_VERSION_STR;

    qDebug() << "+++++++++++++++++++";
    {
        qDebug() << "Test: QSharedPointer<A> sp = QSharedPointer<A>(new A(), &QObject::deleteLater)";
        qDebug() << "Expected:";
        qDebug() << "Destructor of A";
        qDebug() << "Result:";
        QSharedPointer<A> sp = QSharedPointer<A>(new A(), &QObject::deleteLater);
    }
    qDebug() << "-------------------";

    qDebug() << "+++++++++++++++++++";
    {
        qDebug() << "Test: QSharedPointer<A> sp = QSharedPointer<A>(new A())";
        qDebug() << "Expected:";
        qDebug() << "Destructor of A";
        qDebug() << "Result:";
        QSharedPointer<A> sp = QSharedPointer<A>(new A());
    }
    qDebug() << "-------------------";

    qDebug() << "+++++++++++++++++++";
    {
        qDebug() << "Test: QSharedPointer<B> sp = QSharedPointer<B>(new B(), &QObject::deleteLater)";
        qDebug() << "Expected:";
        qDebug() << "Destructor of B";
        qDebug() << "Destructor of A";
        qDebug() << "Result:";
        QSharedPointer<B> sp = QSharedPointer<B>(new B(), &QObject::deleteLater);
    }
    qDebug() << "-------------------";

    qDebug() << "+++++++++++++++++++";
    {
        qDebug() << "Test: QSharedPointer<B> sp = QSharedPointer<B>(new B())";
        qDebug() << "Expected:";
        qDebug() << "Destructor of B";
        qDebug() << "Destructor of A";
        qDebug() << "Result:";
        QSharedPointer<B> sp = QSharedPointer<B>(new B());
    }
    qDebug() << "-------------------";

    qDebug() << "+++++++++++++++++++";
    {
        qDebug() << "INTERESTING CASE";
        qDebug() << "Test: QSharedPointer<A> sp = QSharedPointer<B>(new B(), &QObject::deleteLater)";
        qDebug() << "Expected:";
        qDebug() << "Destructor of B";
        qDebug() << "Destructor of A";
        qDebug() << "Result:";
        QSharedPointer<A> sp = QSharedPointer<B>(new B(), &QObject::deleteLater);
    }
    qDebug() << "-------------------";

    qDebug() << "+++++++++++++++++++";
    {
        qDebug() << "INTERESTING CASE";
        qDebug() << "Test: QSharedPointer<A> sp = QSharedPointer<B>(new B())";
        qDebug() << "Expected:";
        qDebug() << "Destructor of B";
        qDebug() << "Destructor of A";
        qDebug() << "Result:";
        QSharedPointer<A> sp = QSharedPointer<B>(new B());
    }
    qDebug() << "-------------------";

    qDebug() << "+++++++++++++++++++";
    {
        qDebug() << "Test: QSharedPointer<A> sp = QSharedPointer<A>(new B(), &QObject::deleteLater)";
        qDebug() << "Expected:";
        qDebug() << "Destructor of B";
        qDebug() << "Destructor of A";
        qDebug() << "Result:";
        QSharedPointer<A> sp = QSharedPointer<A>(new B(), &QObject::deleteLater);
    }
    qDebug() << "-------------------";

    qDebug() << "+++++++++++++++++++";
    {
        qDebug() << "IT SHOULD NOT WORK AS EXPECTED BEFORE QT 5.8, AS SPECIFIED IN QT DOCUMENTATION";
        qDebug() << "Test: QSharedPointer<A> sp = QSharedPointer<A>(new B())";
        qDebug() << "Expected:";
        qDebug() << "Destructor of B (NOT expected before Qt 5.8)";
        qDebug() << "Destructor of A";
        qDebug() << "Result:";
        QSharedPointer<A> sp = QSharedPointer<A>(new B());
    }
    qDebug() << "-------------------";
}

And this is the result:

QT version  5.7.1
+++++++++++++++++++
Test: QSharedPointer<A> sp = QSharedPointer<A>(new A(), &QObject::deleteLater)
Expected:
Destructor of A
Result:
-------------------
+++++++++++++++++++
Test: QSharedPointer<A> sp = QSharedPointer<A>(new A())
Expected:
Destructor of A
Result:
Destructor of A
-------------------
+++++++++++++++++++
Test: QSharedPointer<B> sp = QSharedPointer<B>(new B(), &QObject::deleteLater)
Expected:
Destructor of B
Destructor of A
Result:
-------------------
+++++++++++++++++++
Test: QSharedPointer<B> sp = QSharedPointer<B>(new B())
Expected:
Destructor of B
Destructor of A
Result:
Destructor of B
Destructor of A
-------------------
+++++++++++++++++++
INTERESTING CASE
Test: QSharedPointer<A> sp = QSharedPointer<B>(new B(), &QObject::deleteLater)
Expected:
Destructor of B
Destructor of A
Result:
-------------------
+++++++++++++++++++
INTERESTING CASE
Test: QSharedPointer<A> sp = QSharedPointer<B>(new B())
Expected:
Destructor of B
Destructor of A
Result:
Destructor of B
Destructor of A
-------------------
+++++++++++++++++++
Test: QSharedPointer<A> sp = QSharedPointer<A>(new B(), &QObject::deleteLater)
Expected:
Destructor of B
Destructor of A
Result:
-------------------
+++++++++++++++++++
IT SHOULD NOT WORK AS EXPECTED BEFORE QT 5.8, AS SPECIFIED IN QT DOCUMENTATION
Test: QSharedPointer<A> sp = QSharedPointer<A>(new B())
Expected:
Destructor of B (NOT expected before Qt 5.8)
Destructor of A
Result:
Destructor of B
Destructor of A
-------------------

EDITING:

I am aware of QObject::deleteLater behaviour, in particular of:

"If deleteLater() is called after the main event loop has stopped, the object will not be deleted. Since Qt 4.8, if deleteLater() is called on an object that lives in a thread with no running event loop, the object will be destroyed when the thread finishes."

But since I am using Qt 5.7, I expect anyway the destructor to be called at the end of my function, that is the only thread that I start (a part the other threads started eventually by Qt)

EDITING 2

(I also edited the title)

(The question maybe is getting more complicated than I expected.)

As far as I know, there is no main thread. All threads are equal, also the first one that should be implicitly created with the main function. It should be not special in any way, is that correct?

Then, why exiting the main thread will not delete the QSharedPointers in the proper way?

The one I posted is a test, in my real application I do have a exec() loop, but the destructors are called AFTER the loop is stopped.

I expect then the deleteLater()s function be called when the thread ends, that is when I exit my main loop.

PS: in order to get all the destructors, I needed a exec() loop, as @dave said, but this would be the second loop in my application, that is:

QTimer::singleShot(0, [](){qApp->exit();});
a.exec(); // a is my QApplication

at the end just before the return.

Why I need it? it is possible to avoid it?

n3mo
  • 663
  • 8
  • 23

1 Answers1

4

Use deleteLater() instead, which will cause the event loop to delete the object after all pending events have been delivered to it.

You do not have an event loop in your program, since there is no QApplication object. Thus, all calls to deleteLater() will not do anything.

If you introduce an event loop by instanciating a QApplication and calling exec on it, you will see the mising destructors being called:

at the end of main():

QApplication a(argc, argv);
a.exec();

additional output:

Destructor of A
Destructor of B
Destructor of A
Destructor of B
Destructor of A
Destructor of B
Destructor of A

EDIT

Since there already was an event loop (as discussed in the comments), the problem was that the QSharedPointer objects got deleted after the event loop had already finished. (Aka exec() returned)

The solution to that was to connect the QCoreApplication::aboutToQuit signal with a function or lambda that will delete the QSharedPointer objects. (Or in this case, clear a list containing those)

This way, the event loop has the chance to destruct the objects being pointed to before finishing.

perivesta
  • 3,417
  • 1
  • 10
  • 25
  • Hi Dave. Yes, I know that (as written in the editing part of my question). But in my program, I call the destructors AFTER the `exec` is stopped. I expect them to be destroyed anyway when the main thread finishes. Why it is not so? – n3mo Feb 11 '19 at 11:11
  • pls have a look at the second editing of my question – n3mo Feb 11 '19 at 11:50
  • Do you not have a call to `exec()` at the end of your main function? AFAIK that is the standard way to run a QT application. If you already had an event loop before, it has probably already stopped and thus cannot delete your objects. – perivesta Feb 11 '19 at 12:07
  • Yes, I have a `exec()` loop at the end of my `main` function. The problem is, that in the `main` function itself, a `QSharedPointer` is created. It is supposed to be deleted when the program ends and it goes out of scope. That means that when `qApp->exit()` is called, this object is still alive. To better explain myself I have something like this: `int main() { QSharedPointer myObject(new MyObject()); ...; QApplication a; int result = a.exec(); return result; }` And somewhere in the program flow, a `qApp->exit()` is called. – n3mo Feb 11 '19 at 13:05
  • Ah ok now I get it. But since there is no event loop running and you just want to exit, why use `deleteLater()` anyway? As I understand it, the whole point of that function is to let the event loop finish delivering signals to that object berfore it is destroyed. But since there cannot be any further signals (because there is no event loop to deliver them) you should just be able to use the normal destructor of `QObject` – perivesta Feb 11 '19 at 13:18
  • Good question. The problem is indeed a bit more convoluted, I just tried to simplify it for my question. It is not a single `QSharedPointer`, it is a list of `QSharedPointer`s. Each pointer in the list can be created at runtime, and deleted at runtime (while the `exec` loop is still running). Then, these `QSharedPointer`s in the list CAN have signal/slot connected, and I must use the `QObject::deleteLater` mechanism in order to avoid a crash. When the application ends, I want to clear the list. At this point `deleteLater` is called again but I do not have a `exec` loop anymore – n3mo Feb 11 '19 at 13:51
  • Yeah that complicates things. At this point I think the best option would be to connect [`QCoreApplication::aboutToQuit`](https://doc.qt.io/qt-5/qcoreapplication.html#aboutToQuit) to a lambda that clears your list. At this point the event loop is still barely running and can clean up your pointers. – perivesta Feb 11 '19 at 14:06
  • Hi Dave, `aboutToQuit` signal with a lambda works. If you could rearrange your answer mentioning this signal, I will accept it. I still do not like the explanation in Qt documentation (I am confused why the main thread does not work as I expected), but well, this is out of the scope of my question I suppose...Thanks for your suggestion. – n3mo Feb 11 '19 at 15:54
  • Yeah, I don't really know about the thread thing either. Maybe it needs to be a thread from a pool in order for that to work. I updated the answer. – perivesta Feb 11 '19 at 19:03