Best practice for passing pointers as sender for async signals
-
Hello. Im new to Qt and dont really unterstand the behaviour of Qt's smart pointers. I read the documentation, searched for examples which fit my needs and experimented with QScoped and QSharedPointer, but couldn't find a fullfilling solution for me.
As a iOS developer, I use a kind of smart pointers with reference counting implemented in NSObject whereby Im able to retain and release pointers when needed. I cant get that to work with Qt.
What I want:
- I want to create a new object on the heap when needed, to prevent memory overhead in the stack.
- This object does an asynchronous job (http communication and result parsing).
- When the job is finished, the object should emit a signal where it passes the result data.
- The receiver reads the result data.
- If the receiver needs a reference of the sender object, it should retain it in a smart pointer.
- Otherwise, the object deletes itself after emitting the signal.
What I did:
@APIRequest::APIRequest() {
// Create pointer in constructor
_pointer = QSharedPointer<APIRequest>(this);
}void APIRequest::loadData() {
connect(this, SIGNAL(didReceiveResponse(int,QVariant*)), this, SLOT(receivedResults(int,QVariant*)));
// internal async http request
doNetworkRequest();
}// result data parsing
void APIRequest::receivedResults(int status,QVariant* data) {
// contains parsed data objects
QList<CustomObject*> resultList;// async job done emit(didLoadObjects(this, &resultList)); // release myself _pointer.clear();
}@
My problems:
- This example works well when I create the object dynamically on the heap.
@
APIRequest *request = new APIRequest();
// connect signals here
request->loadData();
@
But the program crashes when the parent object destroys a static instance from stack with error "double free or corruption"
@
class MainController : public QWidget {
Q_OBJECT
APIRequest _request;
}
@- How to retain the given pointer parameter from slot?
@
// Slot for APIRequest
void MainController::didLoadObjects(APIRequest* request, QList<CustomObject*>* resultList) {
_retainedAPIRequest = QSharedPointer<APIRequest>(request);
}
@Fails with message "QSharedPointer: pointer 0x222ba60 already has reference counting".
Therefore I think, that im totally of the track. Is it possible to implement such a feature and what is the best practice for that?
-
I have continued my tests and got further issues with the pointers:
- Its not possible to cast a QSharedPointer to a subclass of its template class.
Inheritance: ProjectLoader -> APIRequest -> QObject
ProjectLoader receives a signal from its own superclass APIRequest
@
void APIRequest::receivedResults(QSharedPointer<APIRequest> apiRequest, int status,QVariant* data)
@
and should also emit a different signal with itself as shared pointer.
@
emit(didLoadProjects(QSharedPointer<ProjectLoader>(apiRequest)));
@results in an error, that the compiler isnt able to cast APIRequest to ProjectLoader.
@
emit(didLoadProjects(QSharedPointer<ProjectLoader>(this)));
@doesnt work too ("pointer xxx already has reference counting”). Should I call
@
_pointer.clear();
apiRequest.clear();
@before? But the pointer going to be deleted at this moment.
- When I initialize the internal pointer with
@
_pointer = QSharedPointer<APIRequest>(this, &QObject::deleteLater);
@
The "double free" problem is gone for stack objects. But the reference counting doesnt seem to work correctly anymore. I retain the smart pointer in ProjectLoader's signal slot.
@
void MainController::didLoadProjects(QSharedPointer<ProjectLoader> loader) {
qDebug() << "retain" << loader.data();
_projectLoader = loader;
}
@but when MainController goes out scope (on destruction), QSharedPointer<ProjectLoader> _projectLoader does not delete the pointer of ProjectLoader and I got a memory leak.
-
It seems that I found a good solution by using Qt's standard Parent/Child paradigm. I just pass a pointer to the sender as signal parameter, no smart pointer anymore. If anyone of the receivers wants to retain the pointer, it should set its parent to anyone else. Otherwise, after the signal has been emitted, the sender deletes itself by calling deleteLater().
My final code:
APIRequest -> QObject
@
void APIRequest::finished(QNetworkReply* reply) {
// parse and prepare result data;
unsigned int status = 0;
QVariant result;
emit(didReceiveResponse(this, status, &result));
reply->deleteLater();
if (this->parent()==NULL) deleteLater();
}
@ProjectLoader -> APIRequest
@
void ProjectLoader::loadAllProjects() {
this->setPath(QString("/projects"));
connect(this, SIGNAL(didReceiveResponse(APIRequest*, int,QVariant*)), this, SLOT(receivedResults(APIRequest*,int,QVariant*)));
this->get();
}void ProjectLoader::receivedResults(APIRequest* apiRequest, int status, QVariant* data) {
// create a list of projects
QList<Project*> _projectList;
emit (projectsLoaded(this));
}
@MainController
@
void MainController::loadProjects() {
ProjectLoader loader = new ProjectLoader();
connect(loader, SIGNAL(projectsLoaded(ProjectLoader)), this, SLOT(showProjects(ProjectLoader*)));
loader->loadAllProjects();
}void MainController::showProjects(ProjectLoader* loader) {
// parent of ProjectLoader/APIRequest must not be null
loader->setParent(this);// this is optional _projectLoader = QSharedPointer<ProjectLoader>(loader);
}
@Works like a charm, but not for objects on the stack:
MainController
@
void MainController::loadProjectsAndSecrets() {
connect(&_stackProjectLoader, SIGNAL(projectsLoaded(ProjectLoader*)), this, SLOT(showProjects(ProjectLoader*)));
_stackProjectLoader.loadAllProjects();
}void MainController::showProjects(ProjectLoader* loader) {
// do nothing with the loader here
}
@After emitting the projectsLoaded() signal, the program crashes with "double free or corruption" because its parent is null.
-
Be aware that the smart pointer itself is reference counted, not the object it points to. This means, that if you create two independent smart pointers for a single object, you will end up having two independent smart pointers, each counting their references own their own.
If you need another reference to an object already managed by a smart pointer you will have to copy the existing smart pointer (either using the copy constructor or assignent operator) which automatically increases the reference count. If such a copy is deleted the reference count is decremented and the object is, if the reference count has reached zero, is deleted.
@
APIRequest *apiRequest = new APIRequest;// WRONG
// sharedPointerA and sharedPointerB are two independent smart pointers
// both having a reference count of 1.QSharedPointer<APIRequest> sharedPointerA(apiRequest);
QSharedPointer<APIRequest> sharedPointerB(apiRequest);// RIGHT
// sharedPointerC, sharedPointerD and sharedPointerE are copies of the
// same smart pointer, all having a reference count of 3QSharedPointer<APIRequest> sharedPointerC(apiRequest);
QSharedPointer<APIRequest> sharedPointerD(sharedPointerC);
QSharedPointer<APIRequest> sharedPointerE = sharedPointerD;
@
Again, only the smart pointer itself is reference counted (and threadsafe), not the object it points to. This also means the code like this possible (although it is horribly wrong).
@
APIRequest *apiRequest = new APIRequest;// WRONG
// sharedPointerF and sharedPointerG are both correctly reference counted,
// but the object they point to has already been deleted, and as soon as
// the reference count reaches 0, and the smart pointer tries to delete
// the object the application will crash, as the object is deleted twice.
QSharedPointer<APIRequest> sharedPointerF(apiRequest);
QSharedPointer<APIRequest> sharedPointerG = sharedPointerF;
delete apiRequest;
@
For the same reason smart pointers must not be used with stack objects. Stack object are automatically deleted as soon as the leave their scope.
@
// WRONG
// apiRequest leaves scope and is automatically deleted. sharePointerH
// leaves scope as well and tries to delete the apiRequest again.
{
APIRequest apiRequest;
QSharedPointer<APIRequest> sharedPointerH(&apiRequest);
}// WRONG
// sharedPointerI leaves scope and deletes apiRequest. When apiRequest
// goes out of scope it is automatically deleted again.
{
APIRequest apiRequest;
{
QSharedPointer<APIRequest> sharedPointerI(&apiRequest);
}
}
@
You should generally never let pointers to stack object leave the scope, as the reference object will be destroyed as soon as the scope is left. Therefor you will have to use a heap object if you want to pass an object from a signal to various slots.This means for your example that your signal will have to emit a shard pointer to the APIRequest object, not the object itself (which, again, is not reference counted).
@
void didLoadObjects(QSharedPointer<APIRequest> request, QList<CustomObject*>* resultList);
@
For debugging purposes you should define <code>QT_SHAREDPOINTER_TRACK_POINTERS</code> right before including the smart pointer headers. By doing so, Qt keeps track of objects already beeing managed by a smart pointer and issues an error if you try to create another smart pointer for this object. -
@
void QNetworkAccessManager::finished ( QNetworkReply * reply )
@Also passes heap objects without being tracked by a shared pointer, isn't it? How is reply getting managed when I dont call deleteLater().
What should I do, when I want to pass a smart pointer and I dont know, if this object already got referenced counted by another shared pointer.
@
QSharedPointer<APIRequest> request = QSharedPointer<APIRequest>(new APIRequest());
connect(request.data(), SIGNAL(didLoadObjects(QSharedPointer<APIRequest>, QList<CustomObject*>)), this, SLOT(objectsLoaded(QSharedPointer<APIRequest>,QList<CustomObject*>)));
request->loadObjects();
@ -
[quote author="Rebell" date="1334246541"]QNetworkAccessManager::finished() also passes heap objects without being tracked by a shared pointer, isn't it? How is reply getting managed when I dont call deleteLater().[/quote]It isn't. If you don't delete the reply you are leaking memory.
[quote author="Rebell" date="1334246541"]What should I do, when I want to pass a smart pointer and I dont know, if this object already got referenced counted by another shared pointer.[/quote]There is basically nothing you can do. It is your responsibility as the developer to define the lifetime of objects, who creates them, who deletes them and wheter you are using smart pointers to do (a part of) this job for you.
If an object created by you leaves your authority you either have to let it leave as a smart pointer (as in your example) so that it is clear that this object is managed by a smart pointer or you document the lifetime of the object and who is responsible for deleting it.
[quote]"QNetworkAccessManager":http://qt-project.org/doc/qt-4.8/qnetworkaccessmanager.html#details ... Note: After the request has finished, it is the responsibility of the user to delete the QNetworkReply object at an appropriate time. Do not directly delete it inside the slot connected to finished(). You can use the deleteLater() function. ...[/quote]