QList полиморфного класса с копированием при записи?

Я пытаюсь создать QList полиморфного типа, который по-прежнему использует Qt неявное разделение.

Мой конкретный пример использования — передача элементов, хранящихся в QList, QtConcurrent :: сопоставляются. Все элементы происходят от базового класса, который определяет виртуальную функцию, которую вызовет QtConcurrent :: mapped. Большая часть хранимых данных будет зависеть от дочернего класса. Эти элементы могут быть отредактированы после начала работы с потоками, оставляя мне две основные опции: блокировка или копирование данных. Я не хочу вставлять блокировки, потому что это устранит большую часть цели использования дополнительных потоков. Создание полных копий моих данных также кажется совершенно нежелательным. Вместо этого я хотел бы использовать неявное совместное использование Qt только для создания копий элементов данных, которые я изменяю, однако я не могу создать QList полиморфного типа, который все еще использует неявное совместное использование.

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

QList<Base> list;
Derived derived_obj;
list.append(derived_obj); // this fails

Однако добавление дочернего класса в QList родительского класса не будет работать, и стандартный ответ вместо этого использовать QList QSharedPointers для базового класса, который будет принимать добавление указателя на дочерний класс.

QList<QSharedPointer<Base> > pointer_list;
QSharedPointer<Derived> derived_pointer;
pointer_list.append(derived_pointer); // this works but there is no copy-on-write

Если я использую QList из QSharedPointers, то будет копироваться именно QSharedPointer, а не мой полиморфный класс, что означает, что я потерял функциональность копирования при записи, которую я хотел бы.

Я также смотрел на использование QList QSharedDataPointers.

QList<QSharedDataPointer<Base> > data_pointer_list;
QSharedDataPointer<Derived> derived_data_pointer;
list.append(derived_data_pointer); // this fails

Однако, как и сам QList, QSharedDataPointers, по-видимому, не принимают полиморфные типы.

6

Решение

Это не удается:

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.

Сам тип не является полиморфным, чтобы сэкономить на размере указателя виртуальной таблицы. as() функция действует как dynamic_cast() будет, перенаправив полиморфизм в PIMPL.

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.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]