QSharedData и наследование
Я пытаюсь сделать систему типов при использовании QSharedData
, Идея проста, будет несколько разных типов данных, каждый из которых будет производным от базового абстрактного класса. Я хочу использовать QSharedData
хранить фактические данные в каждом из них, но каждый из производных классов будет иметь разные данные, хранящиеся внутри. Я пытаюсь сделать самый простой пример сейчас, и у меня есть некоторые проблемы.
Допустим, это мои базовые чисто виртуальные классы:
class cAbstractData: public QSharedData
{
public:
cAbstractData(){ }
virtual int type() = 0;
};
class cAbstractValue
{
public:
cAbstractValue(){ }
virtual int type() = 0;
protected:
QSharedDataPointer<cAbstractData>data_;
};
Теперь предположим, что я хочу создать класс для представления одного значения (в качестве минималистического примера). Я извлекаю cAtomicValue
из базового класса значений, и я также извлекаю класс данных для хранения значения:
class cAtomicData:public cAbstractData
{
public:
cAtomicData() { value_ = 0; }
int type(){ return 1; }
QVariant value_;//the actual value
};
class cAtomicValue:public cAbstractValue
{
public:
cAtomicValue() {
data_ = new cAtomicData;//creating the data object.
}
int type(){ return 1; }
};
Теперь на этом этапе он работает просто отлично, и в отладчике я вижу правильный тип указателя. Но теперь я хочу добавить функцию для установки и получения значения, и я не понимаю, как это сделать. Давайте возьмем сеттер в качестве примера. Чтобы установить значение, мы должны получить доступ к value_
член cAtomicData
класс через data_
член cAtomicValue
учебный класс. Однако так как data_
содержит указатель на базовый класс (cAbstractData
) Мне придется привести его к нужному типу (cAtomicData
) как-то. Я пытался сделать это:
template<class T> void set( T value )
{
static_cast<cAtomicData*>(data_.data())->value_ = value;
}
это явно не работает, потому что это называется detach()
и пытается создать копию базового класса, чего не может сделать, поскольку базовый класс является чисто виртуальным. Затем я попытался привести указатель сам:
static_cast<cAtomicData*>(data_)->value_ = value;
но я получаю invalid static_cast ...
ошибка.
Как мне это сделать, и я вообще правильно делаю это правильно?
4 ответа
Я не вижу способа добиться того, что вы пытаетесь здесь. Как вы обнаружили, QSharedDataPointer
должен быть основан на фактическом типе, который это содержит.
Вы можете сделать свой базовый класс шаблоном, например
template<class T>
class cAbstractValue
{
public:
cAbstractValue(){ }
virtual int type() = 0;
protected:
QSharedDataPointer<T> data_;
};
Но я не уверен, что вижу, какую пользу ты бы получил от этого.
Вы можете переключиться на QExplicitlySharedDataPointer
вместо QSharedDataPointer
, Таким образом detach()
не будет вызываться всякий раз, когда вы пытаетесь получить неконстантный указатель на cAbstractData
объект, который включает в себя приведение QExplicitlySharedDataPointer<cAbstractData>
возражать против QExplicitlySharedDataPointer<cAtomicData>
объект. Тем не менее, вам нужно будет позвонить detach()
вручную каждый раз, когда вы хотите внести изменения в cAbstractData
если вы собираетесь использовать копирование при записи. Может быть, вы можете написать класс-обертку, чтобы выполнить отсоединение для вас.
Этот метод может быть предпочтительнее, чем использование QSharedPointer
, так как QExplicitlySharedDataPointer
тот же размер, что и обычный указатель (и, следовательно, сохраняет двоичную совместимость), в то время как QSharedPointer
в два раза больше (см. эту запись в блоге).
Изменить: Обратите внимание, что актерский состав от QExplicitlySharedDataPointer<cAbstractData>
в QExplicitlySharedDataPointer<cAtomicData>
является статическим, поэтому вам нужно будет гарантировать, что объект, на который ссылаются, фактически является объектом типа cAtomicData
(или подкласса), или поведение при использовании указателя может быть неопределенным.
У меня была похожая проблема в моем приложении, и вот как я ее решил. у меня есть BaseClass
который реализован с использованием идиомы Pimpl и QExplicitlySharedDataPointer
указывая на BaseClassPrivate
, Этот класс наследуется DerivedClass
чей частный член DerivedClassPrivate
наследуя BaseClassPrivate
,
BaseClassPrivate
имеет одного члена с плавающей точкой по имени baseParam
а также DerivedClassPrivate
имеет другой параметр с именем float derivedParam
,
Я решил эту проблему следующим образом:
Определить защищенный конструктор
BaseClass(BaseClassPrivate* p)
Это используется для создания новых производных классов с указателем на
DerivedClassPrivate
Определить виртуальный
clone()
метод в обоихBaseClassPrivate
а такжеDerivedClassPrivate
Этот метод вызывается для правильного копирования закрытого класса всякий раз, когда требуется глубокая копия. Таким образом, вместо вызова 'QExplicitlySharedDataPointer::detach()', мы проверяем, является ли счетчик ссылок QSharedData больше 1, и затем вызываем клон. Обратите внимание, что QSharedData::ref отсутствует в документации, поэтому это может измениться в любое время (даже если это вряд ли произойдет в ближайшее время).
Статическое приведение указателя d в
DerivedClass
Я считаю удобным определить частный
dCasted()
функция.
Чтобы проверить это виртуальная функция foo()
вводится в BaseClassPrivate
а также DerivedClassPrivate
, который возвращает либо baseParam
или же derivedParam
соответственно.
Вот код:
BaseClass.h
class BaseClass
{
public:
BaseClass() : d(new BaseClassPrivate()) {}
BaseClass(const BaseClass& other) : d(other.d) {}
BaseClass& operator =(const BaseClass& other) {d = other.d; return *this;}
virtual ~BaseClass() {}
float baseParam() const {return d->baseParam;}
void setBaseParam(float value) {
detach(); // instead of calling d.detach()
d->baseParam = value;
}
float foo() const {return d->foo();}
protected:
BaseClass(BaseClassPrivate* p) : d(p) {}
void detach() {
// if there's only one reference to d, no need to clone.
if (!d || d->ref == 1) return; // WARNING : d->ref is not in the official Qt documentation !!!
d = d->clone();
}
QExplicitlySharedDataPointer<BaseClassPrivate> d;
};
DerivedClass.h
class DerivedClass : public BaseClass
{
public:
DerivedClass() : BaseClass(new DerivedClassPrivate()) {}
float derivedParam() const {return dCasted()->derivedParam;}
void setDerivedParam(float value) {
detach(); // instead of calling d.detach();
dCasted()->derivedParam = value;
}
private:
DerivedClassPrivate* dCasted() const {return static_cast<DerivedDataPrivate*>(d.data());}
};
BaseClassPrivate.h
class BaseClassPrivate : public QSharedData
{
public:
BaseClassPrivate() : QSharedData(), baseParam(0.0) {}
BaseClassPrivate(const BaseClassPrivate& other) :
QSharedData(other), baseParam(other.baseParam) {}
virtual ~BaseClassPrivate() {}
float baseParam;
virtual float foo() const {return baseParam;}
virtual BaseClassPrivate* clone() const {
return new BaseClassPrivate(*this);
}
};
DerivedClassPrivate.h
class DerivedClassPrivate : public BaseClassPrivate
{
public:
DerivedClassPrivate() : BaseClassPrivate(), derivedParam(0.0) {}
DerivedClassPrivate(const DerivedClassPrivate& other) :
BaseClassPrivate(other), derivedParam(other.derivedParam) {}
float derivedParam;
virtual float foo() const {return derivedParam;}
virtual BaseClassPrivate* clone() const {
return new DerivedClassPrivate(*this);
}
};
Теперь мы можем сделать такие вещи, как:
Вызов виртуальных функций:
DerivedClass derived;
derived.setDerivedParam(1.0);
QCOMPARE(derived.foo(), 1.0); // proving that DerivedClassPrivate::foo() is called
Сделать копии из DerivedClass
в BaseClass
правильно:
BaseClass baseCopy = derived;
QCOMPARE(baseCopy.foo(), 1.0); // proving that DerivedClassPrivate::foo() is called
// even after copying to a BaseClass
Сделать копии из BaseClass
в BaseClass
уважая исходный класс, а также сделайте правильное копирование при записи:
BaseClass bbCopy(baseCopy); // make a second copy to another BaseClass
QCOMPARE(bbCopy.foo(), 1.0); // still calling DerivedClassPrivate::foo()
// copy-on-write
baseCopy.setBaseParam(2.0); // this calls the virtual DerivedClassPrivate::clone()
// even when called from a BaseClass
QCOMPARE(baseCopy.baseParam(), 2.0); // verify the value is entered correctly
QCOMPARE(bbCopy.baseParam(), 1.0); // detach is performed correctly, bbCopy is
// unchanged
QCOMPARE(baseCopy.foo(), 1.0); // baseCopy is still a DerivedClass even after detaching
Надеюсь это поможет
Начиная с Qt 4.5 вы можете реализовать ::clone()
функция для вашего типа:
Эта функция предназначена для поддержки "конструкторов виртуальных копий" для ваших собственных типов. Для этого вам следует объявить специализацию шаблона для этой функции для вашего собственного типа, как в примере ниже:
template<> EmployeeData *QSharedDataPointer<EmployeeData>::clone() { return d->clone(); }
В приведенном выше примере специализация шаблона для функции clone() вызывает виртуальную функцию EmployeeData::clone(). Класс, производный от EmployeeData, может переопределить эту функцию и вернуть правильный полиморфный тип.
Эта функция была введена в Qt 4.5.
Я так и сделал, и это работает.
Ваш абстрактный базовый класс и все производные классы должны реализовать virtual BaseClass* clone()
функция, из которой вы бы позвонили QSharedDataPointer::clone()
или вам нужен какой-то другой метод (например, фабрика), чтобы создать новый экземпляр с тем же содержимым, что и d
,