PDA

View Full Version : QSharedPointer with signals and slots



xtal256
25th June 2012, 08:00
I have recently started using QSharedPointer for a particular type of object which is shared between many classes. Previously i had just used a raw pointer and managed the creation and deletion of the objects in one place. But now i realise it will be much easier (and actually necessary) to do reference counting.

For some reason, there are very few examples out there on how to use QSharedPointer, so i find myself posting here.

One problem i have ran into is using signals and slots with the objects that are shared-pointed-to. Previously i had done this:

MyObject* object; // Subclass of QObject.
AnotherObject* something;
...
connect(object, SIGNAL(updated()), something, SLOT(update())); // Notify when my object has changed

But now that i have changed MyObject* to QSharedObject<MyObject> (typedef'd as MyObjectPtr), the compiler complains:


error C2664: 'bool QObject::connect(const QObject *,const char *,const QObject *,const char *,Qt::ConnectionType)' : cannot convert parameter 1 from 'MyObjectPtr' to 'const QObject *'
No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called


I read that i need to use Q_DECLARE_METATYPE and qRegisterMetaType, but i'm not sure about that.
QObject::connect takes a pointer, but here i am passing a QSharedPointer object. Would doing the above allow that type to be passed? Do i still need to do that if the MyObject class is already a subclass of QObject?
And is it safe to just pass the raw pointer obtained from object.data()?

amleto
25th June 2012, 08:21
But now that i have changed MyObject* to QSharedObject<MyObject> (typedef'd as MyObjectPtr), the compiler complains:
whats a QSharedObject ?

Added after 4 minutes:


And is it safe to just pass the raw pointer obtained from object.data()?
Why wouldn't it be?

xtal256
26th June 2012, 01:13
whats a QSharedObject ?
Oops, i mean't QSharedPointer. I guess i didn't proof-read my post good enough. :(



And is it safe to just pass the raw pointer obtained from object.data()?
Why wouldn't it be?
I don't know, that's why i'm asking :). I know not to go passing the QSharedPointer's internal raw pointer around, because then it cannot do reference counting properly. But i'm not sure how signals and slots handle it. That is, i don't know if Qt would internally hold onto the pointer somewhere.
Then again, since all signals to and from the object are disconnected when it is destroyed, i guess there is no chance of a dangling pointer. I just wanted to make sure i was doing it the right way.


EDIT1:
Another problem i just ran into is using shared pointers as keys in a QMap.
For example: QMap<MyObjectPtr, AnotherObject*>

These objects that i am referring to (the one's i call "MyObject") are stored in a list, which is managed by one class. Previously, if any other classes wanted to use those objects, they would keep a pointer to them. No-one but the manager class would delete the objects. So i could do things like use the pointers as a key in a map, because the pointer would always point to the same object.

Now, i would be storing a QSharedPointer object as the key, which is obviously not going to be the same QSharedPointer object that other classes have.


EDIT2:
Actually, i think QSharedData and QSharedDataPointer are probably what i want. Then i can use MyObject in the same manner as the implicitly shared Qt classes like QString. Although i'm still not sure if or how i can use these objects as keys in a QMap or QHash, or connect them to signals.

amleto
26th June 2012, 08:22
"Now, i would be storing a QSharedPointer object as the key, which is obviously not going to be the same QSharedPointer object that other classes have."

Not sure why that is a problem. It shouldn't be. There is no problem using them as keys in maps etc. Your pointers that you were using before - you weren't using the same instance of a pointer to an object across all your classes (you had multiple instances of pointers to a specific instance), so why is the same situation for shared pointer going to be a problem?

shared pointers and qshareddata(pointer) are fundamentally different so they aren't interchangeable (in the general case).

xtal256
27th June 2012, 02:47
"Now, i would be storing a QSharedPointer object as the key, which is obviously not going to be the same QSharedPointer object that other classes have."

Not sure why that is a problem. It shouldn't be. There is no problem using them as keys in maps etc. Your pointers that you were using before - you weren't using the same instance of a pointer to an object across all your classes (you had multiple instances of pointers to a specific instance), so why is the same situation for shared pointer going to be a problem?
Yes, but the difference is that pointers are essentially just integers, and can be compared for equality. Objects cannot, unless they override the "==" operator. Ok, i see that QSharedPointer does indeed do that, so two different QSharedPointer objects that point to the same object will say they are equal. But they do not implement the '<' operator, so i get the following compile error when using a QMap:

error C2678: binary '<' : no operator found which takes a left-hand operand of type 'const MyObjectPtr' (or there is no acceptable conversion)

Should i perhaps use a QHash instead?


EDIT: I just found out (from this post (http://qt-project.org/forums/viewthread/16031/#81223)) that in order for QSharedPointer to work properly, i must only copy an existing shared pointer and not create new ones from the same raw pointer.
So in some cases, where i have code like this:


class MyObject {
...
void doSomething() {
anotherObject->doSomethingWith(this);
}
}

I cannot simply wrap a QSharedPointer (i.e. MyObjectPtr typedef) around "this". If i do that, then "anotherObject" will get a shared pointer with it's own reference count, which will be different to that of the shared pointer that other classes may have.

amleto
27th June 2012, 08:20
Yes, qhash should work in this case (operator== is present).

Does anotherObject really need the shared pointer, or just a weak one? Otherwise I would consider looking at boost shared_from_this

wysota
27th June 2012, 09:08
I cannot simply wrap a QSharedPointer (i.e. MyObjectPtr typedef) around "this". If i do that, then "anotherObject" will get a shared pointer with it's own reference count, which will be different to that of the shared pointer that other classes may have.

I think you misunderstood that post. In the situation you describe you will get 'the same' shared pointer.

This is valid and correct:

int *i = new int(6);
QSharedPointer<int> a(i);
QSharedPointer<int> b = a;

This is not:

int *i = new int(6);
QSharedPointer<int> a(i);
QSharedPointer<int> b(i);

If doSomethingWith() accepts a shared or a weak pointer, you are fine.

xtal256
28th June 2012, 06:53
Actually, i understood the post exactly as you described it. If MyObject::doSomething is called twice, it will be like the "wrong" code you just posted, because a new QSharedPointer will be created each time it's called.

And i'm not sure a weak pointer is right for this occasion.
Basically, i need to pass an instance of MyObject to another object for it to use. So it's more like this:



class MyObject {
...
AnotherObject* makeAnotherObject() {
return new AnotherObject(this);
}
}

class AnotherObject {
MyObject* blah;
AnotherObject(MyObject* o) : blah(o) {}
...
}

MyObject* foo = getObject(); // Could be a subclass
AnotherObject* bar = foo->makeAnotherObject();


Then foo could be deleted at some point while bar still exists (and still wants to use foo). So how do i handle this situation using shared pointers? Keep in mind that the foo instance does not solely belong to bar, so other things will have a reference to it.

wysota
28th June 2012, 08:07
If MyObject::doSomething is called twice, it will be like the "wrong" code you just posted, because a new QSharedPointer will be created each time it's called.
If you make the method accept a shared pointer instead of a regular pointer then no, it will not be the wrong code.


Basically, i need to pass an instance of MyObject to another object for it to use. So it's more like this:



class MyObject {
...
AnotherObject* makeAnotherObject() {
return new AnotherObject(this);
}
}

class AnotherObject {
MyObject* blah;
AnotherObject(MyObject* o) : blah(o) {}
...
}

MyObject* foo = getObject(); // Could be a subclass
AnotherObject* bar = foo->makeAnotherObject();

Convert all "MyObject*" to "QSharedPointer<MyObject>" in the code above (and everywhere else you are using this object) and you'll be fine. Otherwise if you have a place somewhere that keeps the bare pointer, you can't use shared pointers at all anywhere.

amleto
28th June 2012, 08:54
If you make the method accept a shared pointer instead of a regular pointer then no, it will not be the wrong code.


yes it will, because he will make a new shared pointer from 'this' every time he calls the method from that class. There are other classes that already have shared pointer to 'this', and their reference counts wont reflect this extra share.

wysota
28th June 2012, 09:09
Where exactly does this happen in the below code?


class MyObject {
...
QSharedPointer<AnotherObject> makeAnotherObject() {
return QSharedPointer<AnotherObject>(new AnotherObject(this)); // here? So have a map of instances of MyObject wrapped in QWeakPointer and pass that instead of "this"
}
}

class AnotherObject {
QSharedPointer<MyObject> blah;
AnotherObject(QSharedPointer<MyObject> o) : blah(o) {}
...
}

QSharedPointer<MyObject> foo = getObject();
QSharedPointer<AnotherObject> bar = foo->makeAnotherObject();

amleto
28th June 2012, 09:32
Using a weak reference doesn't guarantee the thing is alive when anotherobject wants to use it. If anotherobject only makes sense to be in existence with a live reference to myobject then we're not in a good place.

It seems like the code could do with a bit of a design change tbh - responsibility/encapsulation feels a bit smeared.

wysota
28th June 2012, 09:39
To be honest I fail to see the point of using shared pointers here at all.

amleto
28th June 2012, 09:51
I'm inclined to agree. If the op had all new/delete in a single place and its lifetime is guaranteed to be longer than the objects containing the pointers, then what is the problem?

xtal256
2nd July 2012, 00:28
Well, the problem is that even though i do currently manage new/delete in a single place, other objects may still have a pointer to an object which gets deleted. And while i could notify that other object of it's deletion, i would prefer it to stay alive until this other object is done with it.

I am wondering if i should use QSharedData and QExplicitlySharedDataPointer (because i don't want copy-on-write) for MyObject. That should allow me to pass it around in the same way you do with most Qt classes, and the data will not get copied. That way (i think) MyObject does the reference counting, rather than a QSharedPointer. Or will i just have the same problems as i do now?

wysota
2nd July 2012, 09:07
other objects may still have a pointer to an object which gets deleted
If they do then regardless how smart pointers you use, you have no influence on what others do.

What I usually do is that I don't expose pointers at all. I wrap them into objects and only expose those. Then you can use QSharedPointer or similar solutions to do reference counting on those internal pointers.

xtal256
5th July 2012, 00:19
What I usually do is that I don't expose pointers at all. I wrap them into objects and only expose those. Then you can use QSharedPointer or similar solutions to do reference counting on those internal pointers.

So what's the difference between that and using QSharedData and QExplicitlySharedDataPointer? Could you perhaps give me a small example?

wysota
5th July 2012, 00:22
There is not much of a difference. You can use QExplicitlySharedDataPointer if you want. The main difference is with QSharedPointer you can also use QWeakPointer where in the other case you only have QExplicitlySharedDataPointer. Apart from that the other difference is that QExplicitlySharedDataPointer allows you to "detach" the data, whereas there is no such concept with QSharedPointer.

xtal256
6th July 2012, 00:19
So, in my case, i would do something like this (similar to the Employee example (http://qt-project.org/doc/qt-4.8/qshareddatapointer.html#employee-example)):



class MyObjectData : public QSharedData {
public:
// data members here
int member1;
QString member2;
// ...

MyObjectData(const MyObjectData &other) : // copy contstructor
QSharedData(other),
member1(other.member1),
member2(other.member2) {
}
};

class MyObject : public QObject {
Q_OBJECT
private:
QExplicitlySharedDataPointer<MyObject> d;
public:
MyObject() { d = new MyObjectData(); }
MyObject(const MyObject &other) : d (other.d) {}

int getMember1() const { return d->member1; }
QString getMember2() const { return d->member2; }
void setMember1(int i) { d->member1 = i; }
void setMember2(QString s) { d->member2 = s; }
};


But i also have some subclasses, so i suppose i would do this:


class SubClassData : public MyObjectData {
public:
// more data members
float subclassMember1;
bool subclassMember2;
// ...

SubClassData(const SubClassData &other) :
MyObjectData(other),
member1(other.member1),
member2(other.member2) {
}
};

class SubClass : public MyObject {
Q_OBJECT
public:
SubClass() { d = new SubClassData(); }
SubClass(const SubClass &other) : d (other.d) {}

int getSubclassMember1() const { return d->subclassMember1; }
QString getSubclassMember2() const { return d->subclassMember2; }
void setSubclassMember1(int i) { d->subclassMember1 = i; }
void setSubclassMember2(QString s) { d->subclassMember2 = s; }
};

Then i would pass around these objects by value instead of passing a pointer to them.

That should solve the two questions i had earlier in this thread.
For connecting to signals, i could pass the address of the MyObject object:


MyObject object;
connect(&object, SIGNAL(updated()), something, SLOT(update()));

And for having the object create an instance of another object which takes a reference to the creator, i could do this:


AnotherObject* MyObject::makeAnotherObject() {
return new AnotherObject(*this);
}
// Note: AnotherObject does not need to be reference counted, so a raw pointer can be returned. The caller of this function will take ownership of it. (Perhaps not the best design, but in this case i am only calling this function from one place and i know where the objects are being used).



wysota, i would be interested to see how you would deal with this problem using QSharedPointer. It sounds like you would just do something similar to my example above, but replace QExplicitlySharedDataPointer with QSharedPointer and not have MyObjectData inherit from QSharedData. Is that right?

wysota
6th July 2012, 08:19
This is all wrong, you can't copy objects that inherit QObject. You have to let the QObject legacy go. If you make the private object inherit QObject then detaching will also not make sense and thus you should use QSharedPointer.

The more I think of this, the more I come to a conclusion that you should simply not worry about the object being deleted and just use QPointer or QWeakPointer to detect such situations in the object keeping a weak (i.e. unowned) pointer to the object. If someone deletes that object behind your back then simply don't access it anymore.

xtal256
7th July 2012, 04:34
Yeah, i was thinking that too. In the case of my program, it would only be a minor inconvenience if one cannot access the object that gets deleted. It's not critical to the program's operation.
Thanks for all your help