QSharedData and inheritance
Asked Answered
D

4

6

I'm trying to make a type system while using QSharedData. The idea is simple, there will be a number of different data types, each of which is going to be derived from the base abstract class. I want to use QSharedData to store the actual data in each of them, but each of the derived classes is going to have different data stored inside. I'm trying to make the most basic example now, and having some troubles.

Let's say these are my base pure virtual classes:

class cAbstractData: public QSharedData
{
public:
    cAbstractData(){ }
    virtual int type() = 0;
};

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

Now let's say I want to make a class for representing a single value (as a minmalistic example that is). I'm deriving the cAtomicValue from the base value class, and I am also deriving a data class to hold the value:

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; }
};

Now at this stage it works just fine, and in the debugger I can see the right pointer type. But now I want to add a function for setting and getting the value, and I fail to understand how to do it. Let's take the setter as an example. To set the value, we must access the value_ member of cAtomicData class through the data_ member of the cAtomicValue class. However since the data_ holds a base-class pointer (cAbstractData), I'll have to cast it to the right type (cAtomicData) somehow. I tried doing this:

template<class T> void set( T value )
{
    static_cast<cAtomicData*>(data_.data())->value_ = value;
}

it obviously doesn't work, because it called detach() and tries to make a copy of the base class which it can't since the base class is pure virtual. Then I tried to cast the pointer itself:

static_cast<cAtomicData*>(data_)->value_ = value;

but I'm getting an invalid static_cast ... error.

How do I do it, and am I even doing it the right way fundamentally?

Dive answered 25/9, 2012 at 8:34 Comment(0)
G
2

I don't see any way to achieve what you're attempting here. As you've discovered, QSharedDataPointer needs to be templated on the actual type it contains.

You could make your base class a template, e.g.

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

But I'm not sure I see what benefit you would get from that.

Goliath answered 25/9, 2012 at 12:23 Comment(1)
Thanks. Apparently there's no way indeed. I ended up using QSharedPointer instead.Dive
D
6

You can switch to QExplicitlySharedDataPointer instead of QSharedDataPointer. In that way detach() won't be called whenever you're trying to obtain a non-const pointer to the cAbstractData object, which includes casting the QExplicitlySharedDataPointer<cAbstractData> object to a QExplicitlySharedDataPointer<cAtomicData> object. However, you will need to call detach() manually every time you want to make a modification to the cAbstractData if you are going to use copy-on-write. Maybe you can write a wrapper class to perform the detaching for you.

This method may be prefered over using QSharedPointer, since a QExplicitlySharedDataPointer is the same size as a normal pointer (and hence keeps binary compability) while a QSharedPointer is twice the size (see this blog entry).

Edit: Note that the cast from QExplicitlySharedDataPointer<cAbstractData> to QExplicitlySharedDataPointer<cAtomicData> is static, so you will have to guarantee that the object that is referenced actually is an object of the type cAtomicData (or of a subclass), or the behavior when using the pointer might be undefined.

Disenchant answered 3/10, 2012 at 9:14 Comment(0)
S
4

I had a similar problem in my application and here is how I solved it. I have a BaseClass that is implemented using the Pimpl idiom and QExplicitlySharedDataPointer pointing to BaseClassPrivate. This class is inherited by DerivedClass whose private member is a DerivedClassPrivate inheriting BaseClassPrivate.

BaseClassPrivate has one float member named baseParam and DerivedClassPrivate has another float parameter named derivedParam.

I solved this problem doing the following :

  1. Define a protected constructor BaseClass(BaseClassPrivate* p)

    This is used to instantiate new derived classes with a pointer to DerivedClassPrivate

  2. Define a virtual clone() method in both BaseClassPrivate and DerivedClassPrivate

    This method is called to correctly copy the private class whenever a deep copy is needed. So, instead of calling 'QExplicitlySharedDataPointer::detach()', we check if the QSharedData reference counter is greater than 1, and then we call clone. Please note that QSharedData::ref is not in the documentation so this can change anytime (even though it seems unlikely to happen soon).

  3. Static cast the d pointer in DerivedClass

    I find it convenient to define a private dCasted() function.

To test this the virtual function foo() is introduced in BaseClassPrivate and DerivedClassPrivate, which returns either baseParam or derivedParam accordingly.

Here is the code :

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);
    }
};

Now, we can do things such as :

Call virtual functions :

DerivedClass derived;
derived.setDerivedParam(1.0);
QCOMPARE(derived.foo(), 1.0);   // proving that DerivedClassPrivate::foo() is called

Make copies from DerivedClass to BaseClass correctly :

BaseClass baseCopy = derived;   
QCOMPARE(baseCopy.foo(), 1.0);   // proving that DerivedClassPrivate::foo() is called  
                                 // even after copying to a BaseClass

Make copies from BaseClass to BaseClass respecting the original class and also make a copy-on-write correctly :

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

Hope this helps

Suffocate answered 21/9, 2014 at 10:56 Comment(4)
Wouldn't calling setBaseParam() in the BaseClass create a deep copy even if the reference count of the private data is 1? Wouldn't it be better to first see if the reference is above 1 to call clone()?Carrington
Yes, you're absolutely right. I was thinking about this when I saw your comment. Unfortunately this is a caveat since there is no way of knowing the reference count with QSharedData. I guess templating the base class as suggested by Dan is the best way to go.Suffocate
I suggest having a look at #2693819Suffocate
After having a look in the source code, I found that there is a non-documented public QAtomicInt member named ref in QSharedData which hold the reference counting. I updated my answer to comply with UndeadKernel's comment.Suffocate
G
2

I don't see any way to achieve what you're attempting here. As you've discovered, QSharedDataPointer needs to be templated on the actual type it contains.

You could make your base class a template, e.g.

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

But I'm not sure I see what benefit you would get from that.

Goliath answered 25/9, 2012 at 12:23 Comment(1)
Thanks. Apparently there's no way indeed. I ended up using QSharedPointer instead.Dive
D
0

Since Qt 4.5 you can implement the ::clone() function for your type:

This function is provided so that you may support "virtual copy constructors" for your own types. In order to so, you should declare a template-specialization of this function for your own type, like the example below:

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

In the example above, the template specialization for the clone() function calls the EmployeeData::clone() virtual function. A class derived from EmployeeData could override that function and return the proper polymorphic type.

This function was introduced in Qt 4.5.

I've done so and it works.

Either your abstract base class and all derived classes need to implement a virtual BaseClass* clone() function you'd call from QSharedDataPointer::clone() or you need some other method (e.g. factory) to create a new instance with the same content as d.

Dichroic answered 9/3, 2018 at 6:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.