(Not so much) Fun with QSharedPointer

Qt has a wonderful way of dealing with memory management. The core idea is simple. Most objects have a parent, and when the parent gets destroyed, it will first destroy all its children. Using this technique, you can often write your Qt applications with little to no concern for memory management. Often, you literally don’t have to have a single delete in your entire application. That’s pretty sweet!

In addition, since Qt 4.5 QSharedPointer was introduced, which is very similar in concept to boost::shared_ptr (and thus std::tr1::shared_ptr). I have long been a huge fan of the idea of smart pointers. They solve the need to worry about memory management for almost all usual cases. Unfortunately, when you combine these two concepts, sometimes you can go awry. I was surprised by this one, so I figured I’d shared my findings :-).

First we need some basic code to demonstrate the issue… Let’s suppose we have two trivial classes which inherit from QObject and thus participate in the Qt style memory management system. They might look like this:

Parent.h

class Parent : public QObject {
	Q_OBJECT
public:
	Parent(QObject *parent = 0) : QObject(parent) {
		std::cout << "Parent()" << std::endl;
	}
	
	virtual ~Parent() {
		std::cout << "~Parent()" << std::endl;
	}
};

Child.h

class Child : public QObject {
	Q_OBJECT
public:
	Child(QObject *parent = 0) : QObject(parent) {
		std::cout << "Child()" << std::endl;
	}
	
	virtual ~Child() {
		std::cout << "~Child()" << std::endl;
	}
};

And finally, we have some code to make use of our wonderful code above:

void MyAwesomeFunction(Child *o) {
	std::cout << "Using Object!" << std::endl;
}

int main() {
	Parent *const parent = new Parent(0);
	Child *const child = new Child(parent);
	MyAwesomeFunction(child);
	delete parent;
}

The output of this simple program should be as follows:

Parent()
Child()
Using Object!
~Parent()
~Child()

As you can see, despite only explicitly deleting the parent, the child object gets cleaned up as well! This is great, heck if we made parent an automatic object, we wouldn’t even need to explicitly delete it either. But what if MyAwesomeFunction for whatever reason, took a QSharedPointer instead of a raw pointer? You may just assume that you could write your code to be like this:

void MyAwesomeFunction(const QSharedPointer<Child> &o) {
	std::cout << "Using Object!" << std::endl;
}

int main() {
	Parent *const parent = new Parent(0);
	QSharedPointer<Child> child(new Child(parent));
	MyAwesomeFunction(child);
	delete parent;
}

well, if you made that assumption like I did, you’d be wrong! At best, this code leads to output like this:

Parent()
Child()
Using Object!
~Parent()
~Child()
QObject: shared QObject was deleted directly. The program is malformed and may crash.

At worst, it will crash. The question is, why is this broken? It all boils down to reference counting and who is doing the counting. What we have here, is two separate reference counts on the same object. One maintained by the parent object, one maintained by the QSharedPointer, and they will certainly disagree on when to delete the object. Leading to double free and use after free errors. No Bueno.

But it gets more complex than that! Qt memory management tries its best to let you do manual memory management while still maintaining it’s own system. So if you delete child before the parent is deleted, that’s just fine. The child’s destructor will deregister itself from its parent’s child list. So the following is well formed:

void MyAwesomeFunction(Child *o) {
	std::cout << "Using Object!" << std::endl;
}

int main() {
	Parent *const parent = new Parent(0);
	Child *const child = new Child(parent);
	MyAwesomeFunction(child);
	delete child;
	delete parent;
}

But if the parent happens to be delete’d first, then all hell breaks loose:

void MyAwesomeFunction(Child *o) {
	std::cout << "Using Object!" << std::endl;
}

int main() {
	Parent *const parent = new Parent(0);
	Child *const child = new Child(parent);
	MyAwesomeFunction(child);
	delete parent;
	delete child;
}

Segmentation fault

When we use QSharedPointer, this is essentially what we are doing if the object has a parent! The scary thing is, if the parent outlives the QSharedPointer on average, then the bug doesn’t show up (since then it will act like case #1). Fortunately, starting with Qt 4.8, if you attempt to write this type of broken code, you will see an assert like this:

QSharedPointer: pointer 0x2384d70 already has reference counting

Which at the very least gives us a basic idea that there is something wrong, and it involves a QSharedPointer. It’s a start.

The simplest approach to the problem is to simply not mix and match the two memory management schemes. If you need a QSharedPointer, don’t set the parent. Like this:

void MyAwesomeFunction(const QSharedPointer<Child> &o) {
	std::cout << "Using Object!" << std::endl;
}

int main() {
	Parent *const parent = new Parent(0);
	QSharedPointer<Child> child(new Child());
	MyAwesomeFunction(child);
	delete parent;
}

which will happily output the following:

Parent()
Child()
Using Object!
~Parent()
~Child()

10 thoughts on “(Not so much) Fun with QSharedPointer

  1. David Pinelo

    Very interesting post. Sometimes it’s hard to understand memory management when you see a lot of differents options (scope pointer, shared, shared data, parent-child…). A simple practical sample like you write made things clearer.

  2. Joe Bar

    During a Qt3 to Qt4 port, it’s easy to get wrapped up in Qt class conversions and forget about this interweaving of memory management. Thanks for the concise writeup! You provided a wonderful “Oh yeah!” moment.

  3. R. Stephan

    When reading it was obvious to me you shouldn’t set the parent (or delete it expicitly), even before I read that Qt4.8 complains about it. It’s not as complicated as you make it: The child’s memory simply always is the parent’s memory plus the extra child bits, so if you delete the parent after setting it as part of child, then delete it, you are left with an incomplete child hull. Think graphically!

  4. dashesy

    Just found your blog, am reading some random posts just to enjoy the details. Please write more insight about Qt, I am using Qt for the past year and half, and love the API, am wondering why anybody does not want to use it in just everything. I have persuaded everyone at my work to use Qt, we use it in more and more projects every day, and update our job interview/requirement listing to include Qt :)
    I am using Qt as full fledged GUI applications, as stand-alone libraries (no QObject really), and in cross platform shared libraries, and it just works!

  5. Jose

    Thank you for the post! The “QSharedPointer: pointer 0x2384d70 already has reference counting error” is killing me softly…

  6. su16

    maaaan!
    Don’t use QSharedPointer with QObject, use QPointer instead to provide same counter for object and pointer =) it’s also written in manual to Qt…

  7. Evan Teran Post author

    I cannot find where in the documentation is says not to use QSharedPointer with QObject. In fact, the first example strongly implies the usage of a QObject derived type with QSharedPointer by using deleteLater. As for QPointer, while it “works”, it provides very different functionality. QPointer can become NULL behind your back if the pointer was deleted elsewhere. So it is not “shared” in the same sense as QSharedPointer at all. It just avoids things like use after free errors (they get turned into NULL dereference errors if you fail to check instead). The two are simply different.

  8. jessn

    In general, Smart Pointers are dangerous when using them together with a framework that handles destruction / clean up implicitly. It raises issues such as double deletion of pointers.

    However, I agree with the author that smart pointers can be very convenient, but they must be used with caution. Smart pointers are especially convenient when dealing with remote interfaces to avoid interface leaks hence those can be hard to find in a distributed environment.

  9. Pingback: Les pointeurs intelligents C++ avec Qt 30 minutes par jour

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>