我正在尝试创建一个仍使用Qt' s implicit sharing的多态类型的QList。
我的具体用例是将QList中保存的项目传递给QtConcurrent::mapped。这些项都来自一个基类,它定义了QtConcurrent :: mapped将调用的虚函数。大多数存储的数据将是特定于子类的。线程开始后可以编辑这些项目,留下两个主要选项,锁定或复制数据。我不想坚持使用锁,因为这会消除使用额外线程的大部分目的。同样制作我的数据的完整副本似乎也是不可取的。相反,我想使用Qt的隐式共享来制作我更改的数据项的副本,但是我似乎无法制作仍然使用隐式共享的多态类型的QList。
QList by default uses implicit sharing,乍一看似乎我们已经完成了。
QList<Base> list;
Derived derived_obj;
list.append(derived_obj); // this fails
然而,将子类附加到父类的QList将不起作用,standard answer将使用QSharedPointers的QList到基类,它将接受附加指向子类的指针。
QList<QSharedPointer<Base> > pointer_list;
QSharedPointer<Derived> derived_pointer;
pointer_list.append(derived_pointer); // this works but there is no copy-on-write
如果我使用QSharedPointers的QList,那么将复制QSharedPointer而不是我的多态类,这意味着我已经失去了我想要的写时复制功能。
我还看过使用QSharedDataPointers的QList。
QList<QSharedDataPointer<Base> > data_pointer_list;
QSharedDataPointer<Derived> derived_data_pointer;
list.append(derived_data_pointer); // this fails
然而,与QList本身一样,QSharedDataPointers似乎不接受多态类型。
答案 0 :(得分:2)
这失败了:
QList<QSharedDataPointer<Base>> list;
QSharedDataPointer<Derived> derived(new Derived);
list.append(derived);
注意下面的另一种方法是合并PolymorphicShared
和PolymorphicSharedBase
,直接向QSharedDataPointer
添加多态支持,而不对其提出特殊要求QSharedData
- 派生类型(例如,它不需要明确支持clone
)。这需要更多的工作。以下只是一种工作方法。
QSharedDataPointer
确实是你寻求的答案,绝对可以保持多态QSharedData
。您需要将类型分为基于QSharedData
的层次结构和另一个包含QSharedDataPointer
的并行层次结构。 QSharedDataPointer
通常不是由类的最终用户直接使用。它是实现隐式共享类的实现细节。
为了提高效率,QSharedDataPointer
是一种可以在位级移动的小类型。当存储在各种容器中时,它非常有效 - 特别是在Qt容器中,可以利用类型特征来了解这个属性。使用QSharedDataPointer
的类的大小通常会加倍,如果我们自己使它变成多态,因此它有助于不这样做。当然,指向数据类型可以是多态的。
首先,让我们定义一个相当普遍的基类PIMPL,您将构建层次结构。可以将PIMPL类转储到调试流中并进行克隆。
// 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()
。
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-ptr转换为适当的派生PIMPL类型,并将构造函数参数转发给PIMPL的构造函数。
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()
访问者。
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);
然后,一个不添加新方法的具体类型:
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);
另一种添加方法的类型。
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()
方法是否根据需要抛出:
template <typename F> bool is_bad_cast(F && fun) {
try { fun(); } catch (std::bad_cast) { return true; }
return false;
}
使用隐式共享类型与使用Qt自己的类型没有什么不同。我们也可以使用as
代替dynamic_cast
进行投射。
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&>(); }));
}
输出:
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。