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 ....

Как мне это сделать, и правильно ли я вообще это делаю?


person SingerOfTheFall    schedule 25.09.2012    source источник


Ответы (4)


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

Вы можете сделать свой базовый класс шаблоном, например.

template<class T>
class cAbstractValue
{
public:
    cAbstractValue(){ }
    virtual int type() = 0;
protected:
    QSharedDataPointer<T> data_;
};

Но я не уверен, что вижу, какую пользу ты извлек бы из этого.

person Dan Milburn    schedule 25.09.2012
comment
Спасибо. Видимо действительно никак. Вместо этого я использовал QSharedPointer. - person SingerOfTheFall; 26.09.2012

Вы можете переключиться на QExplicitlySharedDataPointer вместо QSharedDataPointer. Таким образом, detach() не будет вызываться всякий раз, когда вы пытаетесь получить неконстантный указатель на объект cAbstractData, что включает приведение объекта QExplicitlySharedDataPointer<cAbstractData> к объекту QExplicitlySharedDataPointer<cAtomicData>. Однако вам нужно будет вызывать detach() вручную каждый раз, когда вы хотите внести изменения в cAbstractData, если вы собираетесь использовать копирование при записи. Возможно, вы можете написать класс-оболочку для выполнения отсоединения за вас.

Этот метод может быть предпочтительнее использования QSharedPointer, поскольку QExplicitlySharedDataPointer — это то же самое. size как обычный указатель (и, следовательно, сохраняет бинарную совместимость), а QSharedPointer в два раза больше (см. эта запись в блоге).

Редактировать: обратите внимание, что приведение от QExplicitlySharedDataPointer<cAbstractData> к QExplicitlySharedDataPointer<cAtomicData> является статическим, поэтому вам нужно будет гарантировать, что объект, на который ссылаются, действительно является объектом типа cAtomicData (или подкласса), или поведение при использовании указателя может быть неопределенным.

person HelloGoodbye    schedule 03.10.2012

У меня была аналогичная проблема в моем приложении, и вот как я ее решил. У меня есть BaseClass, который реализован с использованием идиомы Pimpl, а QExplicitlySharedDataPointer указывает на BaseClassPrivate. Этот класс наследуется DerivedClass, частным членом которого является DerivedClassPrivate, наследующий BaseClassPrivate.

BaseClassPrivate имеет один элемент float с именем baseParam, а DerivedClassPrivate имеет еще один параметр float с именем derivedParam.

Я решил эту проблему, выполнив следующие действия:

  1. Определить защищенный конструктор BaseClass(BaseClassPrivate* p)

    Это используется для создания экземпляров новых производных классов с указателем на DerivedClassPrivate.

  2. Определите виртуальный метод clone() как в BaseClassPrivate, так и в DerivedClassPrivate

    Этот метод вызывается для правильного копирования закрытого класса всякий раз, когда требуется глубокая копия. Таким образом, вместо вызова 'QExplicitlySharedDataPointer::detach()' мы проверяем, больше ли значение счетчика ссылок QSharedData, чем 1, а затем вызываем клонирование. Обратите внимание, что QSharedData::ref отсутствует в документации, так что это может измениться в любое время (даже если это вряд ли произойдет в ближайшее время).

  3. Статическое приведение указателя d в DerivedClass

    Я считаю удобным определить приватную функцию dCasted().

Чтобы проверить это, виртуальная функция foo() введена в BaseClassPrivate и DerivedClassPrivate, которая возвращает либо baseParam, либо derivedParam соответственно.

Вот код:

Базовый класс.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;
};

Производный класс.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());}
};

Базекласспривате.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);
    }
};

Производный классприват.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

Надеюсь это поможет

person Konstantinos Gaitanis    schedule 21.09.2014
comment
Разве вызов setBaseParam() в BaseClass не создаст глубокую копию, даже если счетчик ссылок на частные данные равен 1? Не лучше ли сначала посмотреть, выше ли ссылка 1, чтобы вызвать clone()? - person UndeadKernel; 23.09.2014
comment
Да, вы абсолютно правы. Я думал об этом, когда увидел ваш комментарий. К сожалению, это предостережение, поскольку нет возможности узнать количество ссылок с помощью QSharedData. Я предполагаю, что шаблон базового класса, предложенный Дэном, - лучший способ. - person Konstantinos Gaitanis; 23.09.2014
comment
Я предлагаю взглянуть на stackoverflow.com/questions/2693319/ - person Konstantinos Gaitanis; 23.09.2014
comment
Просмотрев исходный код, я обнаружил, что в QSharedData есть недокументированный общедоступный член QAtomicInt с именем ref, который содержит подсчет ссылок. Я обновил свой ответ, чтобы он соответствовал комментарию UndeadKernel. - person Konstantinos Gaitanis; 25.09.2014

Начиная с 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.

person Martin Hennings    schedule 09.03.2018