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?
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.
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 :
Define a protected constructor BaseClass(BaseClassPrivate* p)
This is used to instantiate new derived classes with a pointer to DerivedClassPrivate
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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With