c++ - 如何分离 `QObject` 的父子关系的关注点,例如与其他所有者互操作?

标签 c++ qt shared-ptr qobject

考虑这个用例:当 QObject 的生命周期在别处管理时,例如通过 C++ 范围的生命周期(作为局部变量,或作为类成员等),或使用共享指针,其父级不应尝试在 ~QObject() 析构函数中删除它。有没有办法真正将对象的所有权传递给共享指针,以便父级不会尝试删除它?

如果我们不打算拥有所有权,设置父项的原因可能是什么?它们的存在源于这样一个事实,即父子关系在 Qt 中适用于多种用途,并且没有内置的方法来解耦它们:

  1. [GC] 父对象充当子对象的垃圾收集器:如果有子对象在父对象销毁之前存活下来,父对象将销毁并释放它们。

  2. [线程]子线程的亲和性遵循父线程。

    这是在 QObject 开始支持多线程操作并获得 thread 属性时添加到 Qt 4 中的。

  3. [WidgetTree] 小部件使用父子关系作为小部件树中的边。

    • 除非小部件具有 Qt::Window 标志 - 然后它是顶级小部件,并且是其自己的小部件树的根,但仍由父级进行垃圾收集.

近期目标是将 [GC] 与其他功能分离,并允许针对每个对象禁用它。扩展目标是将所有三个功能相互分离。

最佳答案

[GC]

我们可以拦截父级的 ~QObject() 析构函数,并在子列表中的子对象被删除之前从子列表中删除该对象。幸运的是,~QObject在删除其子项之前发出 destroyed 信号。

因此,要将一个对象的父对象更改为非拥有对象的父对象,我们必须只在重要的时候采取行动:当父对象的析构函数被调用时。因此,我们

  1. 拦截父对象的destroyed信号并清除对象的父对象。

  2. 拦截失去 child 的 parent ,或者,如果 parent 没有收到此类事件,则拦截发送给 child 的事件并将它们用作钝钩来检测 parent 是否发生了变化。

这可以在独立的实用程序函数和辅助类中实现,无需修改任何相关对象的代码。

// 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);
}

关于c++ - 如何分离 `QObject` 的父子关系的关注点,例如与其他所有者互操作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55046944/

相关文章:

c++ - Visual Studio 2012 中的 Qt5.0.1 静态链接

Python PyQt 装饰方法触发时出错

c++ - boost::make_shared 底层对象和引用计数对象的内存分配大小在哪里?

c++ - vector<shared_ptr<>> 清除错误

树莓派 : Cannot find GLESv2 的 qt 交叉编译

C++ next_permutation 没有以相反的顺序列出

c++ - 从字符串文字到 char * 的转换

c++ - 从多个线程更新对应于 unordred_map 中不同现有键的值

C++ 入门 5 版 : using get member to initialize another shared_ptr independent object

c++ - 捕获参数是从右值 lambda 复制的吗?