Qt Destructor не отключает сигнал / слот - PullRequest
0 голосов
/ 07 сентября 2018

Я обнаружил такое странное поведение Qt: Я ожидаю, что, если объект разрушен, он автоматически отключает все «входящие» сигналы, связанные с его слотами. Тем не менее, следующий небольшой пример демонстрирует, что если сигнал был испущен деструктором члена класса (автоматически вызывается после деструктора основного класса), он все равно принимается слотом основного класса.

Это нормальное поведение, когда я получаю вызов слота фактически несуществующего объекта? Исходный код файла 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;
};

Файл main.cpp содержит только:

{
    Foo obj;
}

Вывод:

Part::Part()
Foo::Foo()
Foo::~Foo()
Part::~Part()
Foo::slotFunc()

1 Ответ

0 голосов
/ 10 сентября 2018

Просто этого не происходит - по крайней мере, не так, как я понимаю, что вы сделали, и нет, если ваш код принимается за чистую монету. Все соединения, в которых задействован данный объект, отключаются до того, как дочерние объекты этого объекта будут удалены как одно из окончательных действий QObject или QWidget, подвергающихся уничтожению. Так было в Qt задолго до версии 4.8, и я проверил это на 4.8, 5.9, 5.10 и 5.11. Поведение такое же: то, что вы описываете, невозможно.

Приведенный ниже тест провалит утверждение внутри Foo::slot, если слот когда-либо вызывается с foo, имеющим любой тип, отличный от Foo. Я призываю вас скачать код, собрать его и запустить. Утверждения сохраняются в режимах отладки и выпуска (из-за QT_FORCE_ASSERTS), поэтому режим выпуска не делает тест недействительным. Код не должен требовать никаких изменений для компиляции для вас - если это так, он указывает на некоторые другие проблемы, которые должны быть решены в первую очередь, с пониманием, но не пренебрежительно.

Класс Scope является только помощником, используемым в качестве прокси для счетчика программы, и позволяет утверждать, что делает код.

Вывод выглядит следующим образом (первый из 3 протестированных сценариев):

*** 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)

Там будет foo_slot_body в месте, помеченном ****, а также сбой из-за неудачного утверждения. Этого не происходит.

// 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"
...