QSharedPointer with signals and slots
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:
Code:
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:
Quote:
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()?
Re: QSharedPointer with signals and slots
Quote:
But now that i have changed MyObject* to QSharedObject<MyObject> (typedef'd as MyObjectPtr), the compiler complains:
whats a QSharedObject ?
Added after 4 minutes:
Quote:
And is it safe to just pass the raw pointer obtained from object.data()?
Why wouldn't it be?
Re: QSharedPointer with signals and slots
Quote:
Originally Posted by amleto
whats a QSharedObject ?
Oops, i mean't QSharedPointer. I guess i didn't proof-read my post good enough. :(
Quote:
Quote:
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.
Re: QSharedPointer with signals and slots
"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).
Re: QSharedPointer with signals and slots
Quote:
Originally Posted by amleto
"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) 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:
Code:
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.
Re: QSharedPointer with signals and slots
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
Re: QSharedPointer with signals and slots
Quote:
Originally Posted by
xtal256
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:
Code:
int *i = new int(6);
QSharedPointer<int> a(i);
QSharedPointer<int> b = a;
This is not:
Code:
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.
Re: QSharedPointer with signals and slots
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:
Code:
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.
Re: QSharedPointer with signals and slots
Quote:
Originally Posted by
xtal256
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.
Quote:
Basically, i need to pass an instance of MyObject to another object for it to use. So it's more like this:
Code:
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.
Re: QSharedPointer with signals and slots
Quote:
Originally Posted by
wysota
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.
Re: QSharedPointer with signals and slots
Where exactly does this happen in the below code?
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();
Re: QSharedPointer with signals and slots
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.
Re: QSharedPointer with signals and slots
To be honest I fail to see the point of using shared pointers here at all.
Re: QSharedPointer with signals and slots
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?
Re: QSharedPointer with signals and slots
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?
Re: QSharedPointer with signals and slots
Quote:
Originally Posted by
xtal256
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.
Re: QSharedPointer with signals and slots
Quote:
Originally Posted by
wysota
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?
Re: QSharedPointer with signals and slots
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.
Re: QSharedPointer with signals and slots
So, in my case, i would do something like this (similar to the Employee example):
Code:
public:
// data members here
int member1;
// ...
MyObjectData(const MyObjectData &other) : // copy contstructor
member1(other.member1),
member2(other.member2) {
}
};
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:
Code:
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:
Code:
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:
Code:
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?
Re: QSharedPointer with signals and slots
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.