-1

I've found such strange behavior of Qt: I would expect that if the object is destructed, it automatically disconnects all 'incoming' signals connected to its slots. However, the following small example demonstrates that if the signal was emitted from the destructor of the class member (automatically called after the destructor of the main class), it is still received by the slot of the main class.

Is it the normal behavior that I've got a slot call of the actually non-existing object? The source code of the file example.h:

#include <QObject>
#include <iostream>

class Part: public QObject {
    Q_OBJECT
public:
    Part(QObject *parent = nullptr) : QObject(parent) {
       std::cout << "Part::Part()" << std::endl;
    }
    ~Part() {
       std::cout << "Part::~Part()" << std::endl;
       emit someSignal();
   }
signals:
    void someSignal();
};


class Foo: public QObject {
    Q_OBJECT
public:
    Foo(QObject *parent = nullptr) : QObject(parent) {
        m_part = new Part(this);
        std::cout << "Foo::Foo()" << std::endl;
        connect(m_part, &Part::someSignal, this, &Foo::slotFunc);
    }
    ~Foo() {
        std::cout << "Foo::~Foo()" << std::endl;
    }

public slots:
    void slotFunc() {
        std::cout << "Foo::slotFunc()" << std::endl;
    }
private:
    Part *m_part;
};

The main.cpp file contains just:

{
    Foo obj;
}

The output is:

Part::Part()
Foo::Foo()
Foo::~Foo()
Part::~Part()
Foo::slotFunc()
Borys L.
  • 145
  • 1
  • 9
  • 1
    [it should disconnect](https://stackoverflow.com/a/10570876/3985859). Maybe it doesn't work without an QApplication object and/or an event loop? – Karsten Koop Sep 07 '18 at 11:40
  • Yes, it really helps in this case, if I start this not as a console app, but as a usual Widget-app with the event loop. But the next thing, it works as it's written here, where m_part has a type Part * and initialization new Part(this). But if I use std::unique_ptr m_part; with the corresponding initialization m_part = std::make_unique(); the mentioned behaviour appears. – Borys L. Sep 07 '18 at 12:05
  • Can I ask which version of `Qt` you're using? I don't see the slot being called when I try to repro [Suse Linux + Qt5.11.1]. – G.M. Sep 07 '18 at 13:30
  • Also Qt 5.11.1 but under Windows 7 :) – Borys L. Sep 07 '18 at 14:13
  • Please check out this post, https://stackoverflow.com/questions/14024892/is-it-ok-to-emit-a-signal-from-an-objects-destructor-in-qt – m. c. Sep 07 '18 at 15:49
  • @KarstenKoop The signal/slot system doesn't care about `QApplication` except when the effective connection type would be queued, and that's not the case here. All signal-slot connections in this code act like a call-via-a-function-pointer. No event loop needed. – Kuba hasn't forgotten Monica Sep 07 '18 at 18:27

1 Answers1

0

It just plainly doesn't happen - at least not the way I understand what you did, and not if your code is taken at face value. All of the connections that involve a given object get disconnected before the children of that object get deleted as one of the final acts of a QObject or QWidget undergoing destruction. This has been the case in Qt since well before version 4.8, and I have checked it on 4.8, 5.9, 5.10 and 5.11. The behavior is the same: what you describe is not possible.

The test below will fail an assertion inside Foo::slot if the slot is ever called with foo having any type other than Foo. I urge you to download the code, build it yourself and run it. The assertions are preserved in both debug and release modes (due to QT_FORCE_ASSERTS), so release mode doesn't make the test invalid. The code should require no changes to compile for you - if it does, it indicates some other issue that has to be addressed first, and with understanding but not in a dismissive fashion.

The Scope class is only a helper used as a proxy for the program counter, and makes it possible to assert what the code is doing.

The output looks as follows (first out of the 3 scenarios tested):

*** before the application object is created
part_constructor_body *ran*
part_constructor_body *ran*
part_constructor_body *ran*
part_constructor_body *ran*
part_constructor_body *ran*
part_constructor_body *ran*
part_constructor_body *ran*
Foo_Type (enter)
foo_constructor_body (enter)
foo_constructor_body (leave)
foo_destructor_body *ran*
part_destructor_body (enter)
foo_slot_body *ran*
part_destructor_body (leave)
part_destructor_body (enter)
foo_slot_body *ran*
part_destructor_body (leave)
part_destructor_body (enter)
foo_slot_body *ran*
part_destructor_body (leave)
part_destructor_body (enter)
foo_slot_body *ran*
part_destructor_body (leave)
part_destructor_body (enter)
foo_slot_body *ran*
part_destructor_body (leave)
part_destructor_body (enter)
foo_slot_body *ran*
part_destructor_body (leave)
Foo_Type (leave)
part_destructor_body "part4" (enter)
****
part_destructor_body "part4" (leave)

There'd be a foo_slot_body in the spot marked ****, as well as a crash due to failed assertion. That doesn't happen.

// https://github.com/KubaO/stackoverflown/tree/master/questions/object-lifetime-conundrum-52221660
#define QT_FORCE_ASSERTS
#include <QtCore>
#include <memory>

static const char kEnter[8] = "(enter)", kLeave[8] = "(leave)", kBlip[6] = "*ran*";
class Scope {
   Q_DISABLE_COPY(Scope)
   QObject *const obj;
   QByteArray const property;
   static QObject &proprietor() {
      static QObject p;
      return p;
   }
   static void indicate(const char *prop, QObject *obj, const char *event) {
      auto dbg = QDebug(QtMsgType::QtDebugMsg) << prop;
      if (!obj->objectName().isEmpty()) dbg << obj->objectName();
      dbg << event;
      Q_ASSERT(isIn(prop) == (event == kLeave));
      proprietor().setProperty(prop, event == kEnter);
   }

  public:
   enum When { Out = 1, InOut = 2 } const when;
   Scope(const char *p, QObject *o, When w = InOut) : obj(o), property(p), when(w) {
      if (when == InOut) in();
   }
   void in() { indicate(property, obj, kEnter); }
   ~Scope() { indicate(property, obj, kLeave); }
   struct GoIn {
      GoIn(Scope &scope) { scope.in(); }
   };
   static void blip(const char *prop, QObject *o) { Scope::indicate(prop, o, kBlip); }
   static void shred(const char *prop) { proprietor().setProperty(prop, {}); }
   static bool had(const char *prop) { return proprietor().property(prop).isValid(); }
   static bool isIn(const char *prop) { return proprietor().property(prop).toBool(); }
};

class Part : public QObject {
   Q_OBJECT
  public:
   Part(QObject *parent = nullptr) : QObject(parent) {
      Scope::blip("part_constructor_body", this);
      Q_ASSERT(!Scope::isIn("Foo_Type"));
   }
   ~Part() override {
      Scope scope("part_destructor_body", this);
      emit signal();
   }
   Q_SIGNAL void signal();
};

class Foo : public QObject {
   Q_OBJECT
   Scope scope{"Foo_Type", this, Scope::Out};
   Part part1{this};  // a child owned by value - bravo! - the lowest overhead approach
   Part part2;        // ditto, made a child in the constructor's initializer list
   Part part3;        // fine, but not a child of Foo, and thus Foo's `moveToThread()`
                      // will almost always set Part up for undefined behavior
   // the below all have the overhead of an extra indirection - an entirely gratuitous one
   std::unique_ptr<Part> part1b{new Part(this)};
   std::unique_ptr<Part> part2b;
   std::unique_ptr<Part> part3b{new Part};
   // and the snafu
   Part *part4{new Part(this)};
   Scope::GoIn into{scope};

  public:
   Foo(Qt::ConnectionType type = Qt::AutoConnection)
       : part2(this), part2b(new Part(this)) {
      Scope scope("foo_constructor_body", this, Scope::InOut);
      part4->setObjectName("part4");
      for (auto *p :
           {&part1, &part2, &part3, part1b.get(), part2b.get(), part3b.get(), part4})
         connect(p, SIGNAL(signal()), this, SLOT(slot()), type);
   }
   ~Foo() override { Scope::blip("foo_destructor_body", this); }

   Q_SLOT void slot() {
      Scope::blip("foo_slot_body", sender());
      Q_ASSERT(qobject_cast<Foo *>(this));
      Q_ASSERT(Scope::isIn("Foo_Type"));  // equivalent to the foregoing assert
   }
};

int main(int argc, char *argv[]) {
   qDebug() << "*** before the application object is created";
   Foo{};
   QCoreApplication app(argc, argv);
   qDebug() << "*** after the application object is created";
   Foo{};
   qDebug() << "*** with queued connections" << Qt::QueuedConnection;
   {
      Q_ASSERT(Scope::had("foo_slot_body"));
      Scope::shred("foo_slot_body");
      Foo foo3(Qt::QueuedConnection);  // check with queued connections
      QTimer::singleShot(1, &app, SLOT(quit()));
      app.exec();
      Q_ASSERT(!Scope::had("foo_slot_body"));
   }
}
#include "main.moc"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313