PDA

View Full Version : Memory management with assignable types and QHash



Luc4
22nd May 2011, 12:54
Hi! I'm having a relevant doubt so I would like someone to clarify it if possible. I am in this situation: I created a structure which should be assignable, so that I can put it into a container. In this specific case I'm interested in QHash. This is an example:


struct MyStruct
{
QString aString;
QString anotherString;
QImage anImage;
}

as far as I can understand this should be assignable already without default contructor, copy constructor or operator= override.
First question is: if I copy two MyStruct's this way:


MyStruct s;
MyStruct p = s;

Do I use implicit sharing on the two strings and the image? I mean, does the default operator= of the C++ uses operator= on each of the members? So, when one of those is destroyed, the other one remains valid?

But what happens when I insert MyStruct in a QHash? If I define a QHash like QHash<int, MyStruct>, and I insert MyStruct's inside, do I have that all the members are only shallow copied with copy-on-write?
And what happens if I clear the QHash? Do I get that all the values (and keys in case) are destroyed (and member's refcount is decreased)?
And If I placed a pointer to MyStruct (the object is in the heap then)? I read that in that case the memory is not freed and I have to use qDeleteAll, is this correct?
Thanks for any clarification!

helloworld
22nd May 2011, 20:56
If you are not sure what's going on, you can always create your own implicitly shared class to use as value in the QHash, and then output some debug info:


#include <QtGui/QApplication>
#include <QSharedData>
#include <QHash>
#include <QDebug>

class ImplSharedData : public QSharedData
{
public:
ImplSharedData() : QSharedData()
{ qDebug() << "+ImplSharedData data created"; }

~ImplSharedData()
{ qDebug() << "-ImplSharedData data destroyed"; }

ImplSharedData(const ImplSharedData &other) : QSharedData(other)
{ qDebug() << "deep copy of impl. shared data"; }

// ...
};

class ImplSharedClass
{
public:
ImplSharedClass() : d(new ImplSharedData)
{ qDebug() << "+ImplSharedClass object created"; }

~ImplSharedClass() { qDebug() << "-ImplSharedClass object destroyed"; }

ImplSharedClass(const ImplSharedClass &other) : d(other.d)
{ qDebug() << "shallow copy of ImplSharedClass created"; }

ImplSharedClass &operator =(const ImplSharedClass &other)
{
if (this != &other) {
d = other.d;
qDebug() << "shallow copy of ImplSharedClass through assignment";
}
return *this;
}

inline void modify() { d.detach(); } // non-const function that modifies the data

private:
QSharedDataPointer<ImplSharedData> d;
};

struct MyStruct
{
ImplSharedClass shared;
};

then if in main(), you run


MyStruct strukt;
MyStruct strukt2 = strukt;

QHash<int, MyStruct> hash;

hash.insert(1, strukt);
hash.insert(2, strukt2);

this will generate:

+ImplSharedData data created
+ImplSharedClass object created
shallow copy of ImplSharedClass created
shallow copy of ImplSharedClass created
shallow copy of ImplSharedClass created
-ImplSharedClass object destroyed
-ImplSharedClass object destroyed
-ImplSharedClass object destroyed
-ImplSharedClass object destroyed
-ImplSharedData data destroyed

So the ImplSharedData object is destroyed when the hash falls out of scope. Only one ImplSharedData object exists, but four (4) ImplSharedClass objects, all pointing to the same data.

Btw, MyStruct p = s; will actually invoke the copy-constructor, but if you, on the other hand write:


MyStruct p;
p = s;

the assignment operator will be used.

If, instead main() reads something like:


MyStruct *struktPtr1 = new MyStruct;
MyStruct *struktPtr2 = new MyStruct(*struktPtr1);

QHash<int, MyStruct *> hash2;

hash2.insert(1, struktPtr1);
hash2.insert(2, struktPtr2);

you get:

+ImplSharedData data created
+ImplSharedClass object created
shallow copy of ImplSharedClass created

Here nothing gets destroyed when the hash is going out of scope, so like you said you need to do:


MyStruct *struktPtr1 = new MyStruct;
MyStruct *struktPtr2 = new MyStruct(*struktPtr1);

QHash<int, MyStruct *> hash2;

hash2.insert(1, struktPtr1);
hash2.insert(2, struktPtr2);

qDeleteAll(hash2);

This gives:

+ImplSharedData data created
+ImplSharedClass object created
shallow copy of ImplSharedClass created
-ImplSharedClass object destroyed
-ImplSharedClass object destroyed
-ImplSharedData data destroyed

...and all is well.

--

You can also call the modify() function to see what happens when the data is detached (copy-on-write). For example:


struktPtr1->shared.modify();

wysota
22nd May 2011, 22:14
First question is: if I copy two MyStruct's this way:


MyStruct s;
MyStruct p = s;

Do I use implicit sharing on the two strings and the image?
Yes. If a class uses implicit sharing there is no way to "turn it off" so it is always used.


I mean, does the default operator= of the C++ uses operator= on each of the members?
If using operator= can't be avoided (i.e. copy constructor can't be used) then yes.

So, when one of those is destroyed, the other one remains valid?
Yes.


But what happens when I insert MyStruct in a QHash?
The same rules apply.


If I define a QHash like QHash<int, MyStruct>, and I insert MyStruct's inside, do I have that all the members are only shallow copied with copy-on-write?
Yes.


And what happens if I clear the QHash? Do I get that all the values (and keys in case) are destroyed (and member's refcount is decreased)?
Reference count of all the data referenced by the hash is decreased by 1 and if it reaches 0, the data is destroyed.


And If I placed a pointer to MyStruct (the object is in the heap then)?
Then only pointers stored would be destroyed, leaving the referenced data intact (and causing a memory leak)

I read that in that case the memory is not freed and I have to use qDeleteAll, is this correct?
That's correct.

Luc4
22nd May 2011, 23:40
Both of you were very simple and clear. Thank you very much!