首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >多态类的QList?

多态类的QList?
EN

Stack Overflow用户
提问于 2017-06-16 15:44:51
回答 1查看 604关注 0票数 6

我正在尝试创建一个多态类型的QList,它仍然使用Qt的隐式共享

我的具体用例是将QList中持有的项传递给QtConcurrent::mapped。这些项都是从基类中降下来的,基类定义了QtConcurrent::that将调用的虚拟函数。大多数存储的数据都是特定于子类的。这些项可以在线程开始后进行编辑,给我留下两个主要选项:锁或复制数据。我不想锁在里面,因为这将消除使用额外线程的大部分目的。此外,我的数据的完整副本似乎也相当不受欢迎。相反,我希望使用Qt的隐式共享来只复制我更改的数据项,但是我似乎不能制作一个仍然使用隐式共享的多态类型的QList。

默认情况下,QList使用隐式共享。,所以乍一看,我们似乎已经完成了。

代码语言:javascript
复制
QList<Base> list;
Derived derived_obj;
list.append(derived_obj); // this fails

但是,将子类追加到父类的QList将不起作用,标准答案将向基类使用QList of QSharedPointers,基类将接受向子类追加指针。

代码语言:javascript
复制
QList<QSharedPointer<Base> > pointer_list;
QSharedPointer<Derived> derived_pointer;
pointer_list.append(derived_pointer); // this works but there is no copy-on-write

如果我使用QList of QSharedPointers,那么将被复制的是QSharedPointer,而不是我的多态类,这意味着我已经失去了我想要的复制即写功能。

我还研究过如何使用QList of QSharedDataPointers

代码语言:javascript
复制
QList<QSharedDataPointer<Base> > data_pointer_list;
QSharedDataPointer<Derived> derived_data_pointer;
list.append(derived_data_pointer); // this fails

然而,与QList本身一样,QSharedDataPointers似乎不接受多态类型。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-06-16 18:36:04

这是失败的:

代码语言:javascript
复制
QList<QSharedDataPointer<Base>> list;
QSharedDataPointer<Derived> derived(new Derived);
list.append(derived);

Note下面的另一种方法是合并PolymorphicSharedPolymorphicSharedBase,直接向QSharedDataPointer添加多态性支持,而不对QSharedData-derived类型设置特殊要求(例如,它不需要显式支持clone)。这需要更多的工作。以下只是一种工作方法。

QSharedDataPointer确实是您所寻求的答案,并且绝对可以保持多态QSharedData。您需要将类型划分为基于QSharedData的层次结构,以及包装QSharedDataPointer的另一个并行层次结构。QSharedDataPointer通常不打算由类的最终用户直接使用。它是实现隐式共享类的有用的实现细节。

为了提高效率,QSharedDataPointer是一种可以在位级移动的小类型。当存储在各种类型的容器中时,它是非常有效的--特别是在可以利用类型特征来了解此属性的Qt容器中。如果我们使类本身具有多态性,那么使用QSharedDataPointer的类的大小通常会加倍,因此它有助于避免这样做。当然,指向数据类型可以是多态的。

首先,让我们定义一个相当单一的基类PIMPL,您将在此基础上构建层次结构。可以将PIMPL类转储到调试流中,并进行克隆。

代码语言:javascript
复制
// https://github.com/KubaO/stackoverflown/tree/master/questions/implicit-list-44593216
#include <QtCore>
#include <type_traits>

class PolymorphicSharedData : public QSharedData {
public:
   virtual PolymorphicSharedData * clone() const = 0;
   virtual QDebug dump(QDebug) const = 0;
   virtual ~PolymorphicSharedData() {}
};

xxxData类型是PIMPL,不适合最终用户使用.用户应该使用xxx类型本身。然后,这种共享类型封装多态PIMPL,并使用QSharedDataPointer存储PIMPL。它公开了PIMPL的方法。

为了保存虚拟表指针的大小,类型本身并不是多态的。通过将多态性重定向到PIMPL,as()函数充当dynamic_cast()的角色。

代码语言:javascript
复制
class PolymorphicShared {
protected:
   QSharedDataPointer<PolymorphicSharedData> d_ptr;
   PolymorphicShared(PolymorphicSharedData * d) : d_ptr(d) {}
public:
   PolymorphicShared() = default;
   PolymorphicShared(const PolymorphicShared & o) = default;
   PolymorphicShared & operator=(const PolymorphicShared &) = default;
   QDebug dump(QDebug dbg) const { return d_ptr->dump(dbg); }
   template <class T> typename
   std::enable_if<std::is_pointer<T>::value, typename
   std::enable_if<!std::is_const<typename std::remove_pointer<T>::type>::value, T>::type>
   ::type as() {
      if (dynamic_cast<typename std::remove_pointer<T>::type::PIMPL*>(d_ptr.data()))
         return static_cast<T>(this);
      return {};
   }
   template <class T> typename
   std::enable_if<std::is_pointer<T>::value, typename
   std::enable_if<std::is_const<typename std::remove_pointer<T>::type>::value, T>::type>
   ::type as() const {
      if (dynamic_cast<const typename std::remove_pointer<T>::type::PIMPL*>(d_ptr.data()))
         return static_cast<T>(this);
      return {};
   }
   template <class T> typename
   std::enable_if<std::is_reference<T>::value, typename
   std::enable_if<!std::is_const<typename std::remove_reference<T>::type>::value, T>::type>
   ::type as() {
      Q_UNUSED(dynamic_cast<typename std::remove_reference<T>::type::PIMPL&>(*d_ptr));
      return static_cast<T>(*this);
   }
   template <class T> typename
   std::enable_if<std::is_reference<T>::value, typename
   std::enable_if<std::is_const<typename std::remove_reference<T>::type>::value, T>::type>
   ::type as() const {
      Q_UNUSED(dynamic_cast<const typename std::remove_reference<T>::type::PIMPL&>(*d_ptr));
      return static_cast<T>(*this);
   }
   int ref() const { return d_ptr ? d_ptr->ref.load() : 0; }
};

QDebug operator<<(QDebug dbg, const PolymorphicShared & val) {
   return val.dump(dbg);
}

Q_DECLARE_TYPEINFO(PolymorphicShared, Q_MOVABLE_TYPE);

#define DECLARE_TYPEINFO(concreteType) Q_DECLARE_TYPEINFO(concreteType, Q_MOVABLE_TYPE)

template <> PolymorphicSharedData * QSharedDataPointer<PolymorphicSharedData>::clone() {
   return d->clone();
}

帮助器使抽象基类易于使用派生数据类型。它将d转换为适当的派生PIMPL类型,并将构造函数参数转发给PIMPL的构造函数。

代码语言:javascript
复制
template <class Data, class Base = PolymorphicShared> class PolymorphicSharedBase : public Base {
   friend class PolymorphicShared;
protected:
   using PIMPL = typename std::enable_if<std::is_base_of<PolymorphicSharedData, Data>::value, Data>::type;
   PIMPL * d() { return static_cast<PIMPL*>(&*this->d_ptr); }
   const PIMPL * d() const { return static_cast<const PIMPL*>(&*this->d_ptr); }
   PolymorphicSharedBase(PolymorphicSharedData * d) : Base(d) {}
   template <typename T> static typename std::enable_if<std::is_constructible<T>::value, T*>::type
   construct() { return new T(); }
   template <typename T> static typename std::enable_if<!std::is_constructible<T>::value, T*>::type
   construct() { return nullptr; }
public:
   using Base::Base;
   template<typename ...Args,
            typename = typename std::enable_if<std::is_constructible<Data, Args...>::value>::type
            > PolymorphicSharedBase(Args&&... args) :
      Base(static_cast<PolymorphicSharedData*>(new Data(std::forward<Args>(args)...))) {}
   PolymorphicSharedBase() : Base(construct<Data>()) {}
};

现在,拥有PIMPL类型及其载体的并行层次结构是一件很简单的事情。首先,在我们的层次结构中添加两个方法的基本抽象类型。注意PolymorphicSharedBase如何添加正确类型的d()访问器。

代码语言:javascript
复制
class MyAbstractTypeData : public PolymorphicSharedData {
public:
   virtual void gurgle() = 0;
   virtual int gargle() const = 0;
};

class MyAbstractType : public PolymorphicSharedBase<MyAbstractTypeData> {
public:
   using PolymorphicSharedBase::PolymorphicSharedBase;
   void gurgle() { d()->gurgle(); }
   int gargle() const { return d()->gargle(); }
};
DECLARE_TYPEINFO(MyAbstractType);

然后,不添加新方法的具体类型:

代码语言:javascript
复制
class FooTypeData : public MyAbstractTypeData {
protected:
   int m_foo = 0;
public:
   FooTypeData() = default;
   FooTypeData(int data) : m_foo(data) {}
   void gurgle() override { m_foo++; }
   int gargle() const override { return m_foo; }
   MyAbstractTypeData * clone() const override { return new FooTypeData(*this); }
   QDebug dump(QDebug dbg) const override {
      return dbg << "FooType-" << ref << ":" << m_foo;
   }
};

using FooType = PolymorphicSharedBase<FooTypeData, MyAbstractType>;
DECLARE_TYPEINFO(FooType);

以及添加方法的另一种类型。

代码语言:javascript
复制
class BarTypeData : public FooTypeData {
protected:
   int m_bar = 0;
public:
   BarTypeData() = default;
   BarTypeData(int data) : m_bar(data) {}
   MyAbstractTypeData * clone() const override { return new BarTypeData(*this); }
   QDebug dump(QDebug dbg) const override {
      return dbg << "BarType-" << ref << ":" << m_foo << "," << m_bar;
   }
   virtual void murgle() { m_bar++; }
};

class BarType : public PolymorphicSharedBase<BarTypeData, FooType> {
public:
   using PolymorphicSharedBase::PolymorphicSharedBase;
   void murgle() { d()->murgle(); }
};
DECLARE_TYPEINFO(BarType);

我们希望验证as()方法是否根据需要抛出:

代码语言:javascript
复制
template <typename F> bool is_bad_cast(F && fun) {
   try { fun(); } catch (std::bad_cast) { return true; }
   return false;
}

隐式共享类型的使用与使用Qt自己的此类类型没有什么不同。我们还可以使用as而不是dynamic_cast进行强制转换。

代码语言:javascript
复制
int main() {
   Q_ASSERT(sizeof(FooType) == sizeof(void*));
   MyAbstractType a;
   Q_ASSERT(!a.as<FooType*>());
   FooType foo;
   Q_ASSERT(foo.as<FooType*>());
   a = foo;
   Q_ASSERT(a.ref() == 2);
   Q_ASSERT(a.as<const FooType*>());
   Q_ASSERT(a.ref() == 2);
   Q_ASSERT(a.as<FooType*>());
   Q_ASSERT(a.ref() == 1);
   MyAbstractType a2(foo);
   Q_ASSERT(a2.ref() == 2);

   QList<MyAbstractType> list1{FooType(3), BarType(8)};
   auto list2 = list1;
   qDebug() << "After copy:         " << list1 << list2;
   list2.detach();
   qDebug() << "After detach:       " << list1 << list2;
   list1[0].gurgle();
   qDebug() << "After list1[0] mod: " << list1 << list2;

   Q_ASSERT(list2[1].as<BarType*>());
   list2[1].as<BarType&>().murgle();
   qDebug() << "After list2[1] mod: " << list1 << list2;

   Q_ASSERT(!list2[0].as<BarType*>());
   Q_ASSERT(is_bad_cast([&]{ list2[0].as<BarType&>(); }));

   auto const list3 = list1;
   Q_ASSERT(!list3[0].as<const BarType*>());
   Q_ASSERT(is_bad_cast([&]{ list3[0].as<const BarType&>(); }));
}

输出:

代码语言:javascript
复制
After copy:          (FooType-1:3, BarType-1:0,8) (FooType-1:3, BarType-1:0,8)
After detach:        (FooType-2:3, BarType-2:0,8) (FooType-2:3, BarType-2:0,8)
After list1[0] mod:  (FooType-1:4, BarType-2:0,8) (FooType-1:3, BarType-2:0,8)
After list2[1] mod:  (FooType-1:4, BarType-1:0,8) (FooType-1:3, BarType-1:0,9)

列表副本很浅,项目本身也没有被复制:引用计数都是1。分离之后,所有数据项都被复制,但是由于它们是隐式共享的,所以它们只增加它们的引用计数。最后,在修改一个项之后,它会被自动分离,引用计数下降到1。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44593216

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档