Как отделить проблемы отношений между родителями и детьми `QObject`, например, для взаимодействия с другими владельцами? - PullRequest
2 голосов
/ 07 марта 2019

Рассмотрим этот вариант использования: когда временем жизни QObject управляют в другом месте, например, в зависимости от времени жизни C ++ (как локальная переменная или член класса и т. д.) или совместно используемого указателя его родитель не должен пытаться удалить его в деструкторе ~QObject(). Есть ли способ действительно передать владение объектом общему указателю, чтобы родитель не пытался его удалить?

Какие могут быть причины для установления родителя, если мы не намерены владеть? То, что они существуют, следует из того факта, что отношения родитель-потомок присваиваются для нескольких целей в Qt, и нет никакого встроенного способа их разъединения:

  1. [GC] Родитель действует как сборщик мусора для дочерних объектов: если они выживают до уничтожения родителя, родитель уничтожит и освободит их.

  2. [Thread] Родство дочернего потока соответствует родству родителя.

    Это было добавлено в Qt 4, когда QObject начал поддерживать многопоточные операции и получил свойство thread.

  3. [WidgetTree] Виджеты используют отношения родитель-потомок в качестве ребра в дереве виджетов.

    • За исключением случаев, когда виджет имеет флаг Qt::Window - тогда он является виджетом верхнего уровня и является корнем своего собственного дерева виджетов, но по-прежнему собирает мусор у родителя.

Ближайшая цель состоит в том, чтобы отделить [GC] от других функций и разрешить его отключение для каждого объекта. Расширенная цель состоит в том, чтобы отделить все три функции друг от друга.

Ответы [ 2 ]

2 голосов
/ 07 марта 2019

Нет, вы не можете.

То, что вы запрашиваете, эквивалентно двум std::unique_ptr, указывающим на один и тот же ресурс.

Один - ваш пользовательский пользователь C ++, другой - QObject parent.

Существует промежуточное решение для половины вашей проблемы: используйте QPointer.Этот класс содержит указатель QObject, но имеет значение null, если указанный QObject уничтожен.Пользовательский класс RAII WeakQObjectPtr, который содержит QPointer, мог бы тогда делать то, что вы хотите.Однако он не может обрабатывать, например, членов класса или локальные переменные.

Вы находитесь в мире Qt, живете по правилам Qt или не используете их.Последнее не всегда возможно, хотя.

0 голосов
/ 07 марта 2019

[GC]

Мы можем перехватить деструктор ~QObject() родительского объекта и удалить объект из дочернего списка до того, как дочерние объекты в этом списке будут удалены.К счастью, ~QObject испускает сигнал destroyed перед удалением его потомков.

Таким образом, чтобы изменить родителя объекта на не являющегося родителем, мы должны действовать только тогда, когда это важно: когда вызывается деструктор родителей.Таким образом, мы

  1. перехватываем сигнал родителя destroyed и очищаем родителя объекта.

  2. перехватываем родителя, потерявшего ребенка, или, еслиродитель не получает такие события, перехватывает события, отправленные ребенку, и использует их как тупой хук, чтобы определить, изменился ли родитель.

Это может быть реализовано бесплатнопостоянная служебная функция и вспомогательный класс, без необходимости изменять код любого из задействованных объектов.

// https://github.com/KubaO/stackoverflown/tree/master/questions/qobject-sep-concerns-55046944
#include <QtWidgets>

class ParentTracker : public QObject {
   QMetaObject::Connection connection;
   QObject *subjectParent = nullptr;
   inline QObject *subject() const { return parent(); }
   bool eventFilter(QObject *receiver, QEvent *event) override {
      qDebug() << receiver << event->type();
      if (receiver == subject()) {
         // Track parent changes on the child
         if (subject()->parent() != subjectParent) {
            detachFromParent();
            attachToParent();
         }
      } else if (event->type() == QEvent::ChildRemoved) {
         // Track child changes on the parent
         Q_ASSERT(receiver == subjectParent);
         auto *ev = static_cast<QChildEvent *>(event);
         if (ev->child() == subject()) {
            detachFromParent();
         }
      }
      return false;
   }
   void lostParent() {
      subject()->setParent(nullptr);
      detachFromParent();
   }
   void detachFromParent() {
      if (subjectParent) {
         disconnect(connection);
         connection = {};  // free the connection handle immediately
         subjectParent->removeEventFilter(this);
         subjectParent = nullptr;
      }
   }
   void attachToParent() {
      Q_ASSERT(!subjectParent);
      subjectParent = subject()->parent();
      bool snoopChild = !subjectParent;
      {
         auto *widget = qobject_cast<QWidget *>(subject());
         snoopChild = snoopChild ||
                      (widget && widget->testAttribute(Qt::WA_NoChildEventsForParent));
      }

      if (subjectParent) {
         auto *widget = qobject_cast<QWidget *>(subjectParent);
         snoopChild = snoopChild ||
                      (widget && widget->testAttribute(Qt::WA_NoChildEventsFromChildren));
         connection = connect(subjectParent, &QObject::destroyed, this,
                              &ParentTracker::lostParent);
      }
      if (snoopChild)
         subject()->installEventFilter(this);
      else {
         Q_ASSERT(subjectParent);
         subject()->removeEventFilter(this);
         subjectParent->installEventFilter(this);
      }
   }

  public:
   explicit ParentTracker(QObject *child) : QObject(child) {
      Q_ASSERT(subject());
      attachToParent();
   }
};

ParentTracker *detachQObjectOwnership(QObject *child) {
   Q_ASSERT(child && (!child->thread() || child->thread() == QThread::currentThread()));
   QObject *parent = child->parent();
   if (!parent) return nullptr;
   if (parent->thread() != child->thread()) return nullptr;
   return new ParentTracker(child);
}

template <class T> void setup(QPointer<QObject> &parent, QPointer<QObject> &child) {
   parent = new T;
   child = new T(static_cast<T*>(parent.data()));
   parent->setObjectName("parent");
   child->setObjectName("child");
   Q_ASSERT(parent && child);
}

int main(int argc, char *argv[]) {
   QApplication app(argc, argv);
   QPointer<QObject> parent, child, tracker;

   // parent-child ownership
   setup<QObject>(parent, child);
   delete parent;
   Q_ASSERT(!parent && !child);

   // parent-child without ownership
   setup<QObject>(parent, child);
   tracker = detachQObjectOwnership(child);
   delete parent;
   Q_ASSERT(!parent && child && tracker);
   delete child;
   Q_ASSERT(!parent && !child && !tracker);
}
...