How to create shared 'null' private data when using QSharedData?
-
Implicitly shared classes like QString have a static instance of their internal data called shared_null. New instances of those classes refer to that shared data until they are detached-on-write, and the isNull test just checks if the data is referencing that shared data.
I have created an implicitly shared type using QSharedData and QSharedDataPointer, and I wanted to use a similar technique to not create many identical copies of the 'null' private data for every new instance I create.
This is the code I have tried.
@#ifndef SHARED_DATA_HPP
#define SHARED_DATA_HPP#include <QSharedDataPointer>
class PrivateData;
class SharedData
{
public:
SharedData();
SharedData(const SharedData &);
~SharedData();bool isNull() const;
protected:
QSharedDataPointer<PrivateData> d;
};
#endif // ndef SHARED_DATA_HPP@
@#include "shared_data.hpp"
class PrivateData : public QSharedData
{};
static PrivateData shared_null;
SharedData::SharedData()
: d(&shared_null)
{}
SharedData::SharedData(const SharedData & rhs)
: d(rhs.d)
{}
SharedData::~SharedData()
{}
bool SharedData::isNull() const
{
return (d.data() == &shared_null);
}@@#include "shared_data.hpp"
int main(int argc, char * argv[])
{
SharedData sd;return 0;
}@
This code compiles and runs, but will crash when main() exits. When the SharedData object is created, it increments the reference count for shared_null to 1. When main() exits, the object is destroyed, the reference count for shared_null drops to 0 and this causes it to be deleted. Since shared_null wasn't allocated on the heap, this results in an crash.
I have tried various ways to allocate shared_null or to get its reference count set to one before I create any instances of SharedData, but have had no luck.
Any ideas?
-
The reference count for your shared null is zero. Your instantiation will increase it to one, and upon destruction find that ref count is zero again. If ref count is zero, the SharedDataPointer will try to delete the shared data, which in this case will be unsuccessful. I haven't had a look at the QString code, but maybe you overlooked something there.
-
Something I don't understand about this approach is the fact that QSharedDataPointer can be default constructed, which results in a null object. Why then have the explicitly created shared null thing?
-
OK I had a look at the code and QString is one of the classes that has not yet been updated to make use of QSharedData and QSharedDataPointer. QString does a lot of the work that is now in QSharedDataPointer. Therefore I would say there is no need for you to take this same approach.
That means: If you want to have an isNull() function, use the following (or something like it):
@bool MySharedObject::isNull() const
{
return !d;
}@ -
Thanks for your responses.
I understand why the crash is happening, and I have tried to find ways to get the reference count of the shared null data starts as 1 so that the destruction of any actual null objects I created can never set it back to zero, but I haven't found a way of doing it.
I note that all of the classes in Qt that use this 'shared null' technique do not use QSharedData but handle their own reference counting and detach-on-write behaviour, so maybe that is a clue that I am wasting my time with this approach.
The reason why I haven't just left my d-pointer as NULL is that I would have to test for this case in all of the functions where I want to write to the private data and instantiate it where necessary. Also, in the read functions, I would need to return something, but without a shared null instance of the data, I would be instantiating objects to return all the time, which defeats the object of using this approach.
I just liked the elegance of the shared_null idea and wanted to replicate it.
-
In that case, use
@static QSharedDataPointer<PrivateData> shared_null(new PrivateData);@This solves your reference counting problem.
Concerning the checks, you don't have to do that in the write functions. Because of the non-constness of those functions, the QSharedDataPointer creates a new object if necessary. Checks for read data would be necessary, indeed.
-
That's done the trick. So simple, I can't believe I didn't see that. Thanks for your input.