Continuing on the topics I talked about at this year’s aKademy conference.
Most of the things I want to write here are not new to people who are immersed in C++ and follow the books/presentations by Alexandrescu, Sutter, and others. But, I’ve found that in our Qt-sub-culture, it is often not the case.
Qt is very good at hiding the ugly parts of C++, but at the same time, it sometimes hides too much.
One of the first things you learn in Qt is about the object trees and ownership where the parent obect conviniently destroys its children on its destruction.
IMO, this is one of the nicest features of Qt. But, sometimes, it tends to provide a sense of false security. As trivial example, imagine the following:
It looks nice - we are creating a new object, we pass it a parent (this), so we do not need to worry about its destruction ourselves. The issue is that the parent is often a long-lived object like a QCoreApplication or a main window. This tends to end up in the thread object not being destroyed until the application has been terminated.
Now imagine that the above object is an image cache, and you’ll have a quite substantial memory leak.
The parent-child ownership is a silver bullet in a lot of cases, but not all.
What does a pointer mean?
So, lets return to the topic at hand - API design. One of the biggest problems when using a new library is what the following declaration means:
Namely, the question here is what does the ‘SomeType *’ mean. It can mean quite a few things:
- (static) singleton instance
- should be disposed by the user
- creator-owned, creator disposes of it
- an optional value? (for example, parsing a number from a string could return a null if parsing failed)
- position in an array? (this one is rarely used nowadays, we have iterators for that case)
The problem here is deducing who owns the returned object, and are we guaranteed to get a non-null result at all. It can not be deduced without reading the documentation, and it could and should be.
Since a singleton should be always present, there is really no point in making it a pointer at all. Implementing a proper singleton should be as easy as:
It is thread-safe (in C++11), its declaration clearly states that it returns a non-null object, and an object with a long lifetime.
The next are the factory functions that return an instance of the object whose owner should be the callee, and the callee is responsible for its destruction:
Even if the callee forgets to save the returned value, nothing will be leaked.
3. Caches, ref-counted singletons, etc.
When we have a function that returns something that can be destroyed at any time, or that should be destroyed only after everyone stops using it, we can simply write
The first one tells the callee that the object will exist for as long as he wants to use it, but without the guarantee that it will be destroyed immediately after.
While the later says that it has been given an object that could go away at any point in time.
4. Optional result values
The last use-case is the most problematic one. Not because it has not been solved, but rather because the necessary class has not yet been provided in C++.
When it becomes the part of the standard, it will look something like this:
For the time being, you could use boost::optional, pair<bool, SomeType> and similar.
If you wanted to give the failure error as well, without resorting to throwing exceptions all over the place, my advice is to go and watch Alexandrescu’s talk on “Systematic error handling in C++”.
Now, after we saw what can be done to the API to make the user’s life easier when it comes to memory management, just a very short note about the exception safety.
There is a reason why std::stack does not have a method pop that returns a value, but has the separated top and pop. This is, again, one of the things that should be known to most c++ developers, yet sometimes you can even find some c++ book authors that take jabs at the standard committee for making it that way, and not going for the more convenient API.
I suggest everyone to look at some writings about this - the issue itself gives a nice overview of things to watch out for when designing API which should behave well in the exception-enabled environment.