Monday, October 08, 2007

What I learned from Qt that made me a better programmer in C++

Reginald Braithwaite says he would love to hear stories about how programmers learned from X that makes me a better programmer in Y. So here are a few quick thoughts on how the Qt library made me a better C++ programmer.

1) The API matters, a lot


In a way Trolltech is in the business of selling good API's. By making their API consistent across Qt, it is easier as developers to understand how the class works and one can often guess correctly what an API will be without having to look it up. You wont find functions that take six bool arguments or function names that are overly vague when a better one will do. Each new function gets multiple API reviews and before every release all new functions and classes get a final review. Check out this excellent article on designing C++ APIs for some good examples.

Every time you add a new class or function you are introducing new API. Even if that API is only for yourself it is something that you will have to use and later read. On personal projects taking two minutes to step back and cleanup the API will payoff in the long run. Your code will be more consistent and when someone else comes along to check it out they will not only be happier with the cleaner API, but have an easier time getting up to speed and more likely to contribute back.

2) How to hide your private data (or working around one of c++'s flaws)


When building libaries in C++ there are a lot of rules you have to follow to make sure that you do not break binary compatibility.

The first and best step you can take is to make sure that your API is good (see #1). This goes a long way in fixing design issues before you can't anymore. Walk though use cases and make sure that the class provides what is needed. Hopefully most of the time you should be able to get away with just adding a new function or two, but the core of the class will be stable.

But behind the public API there are tricks you can used to hide all of the internals of the class in a way that lets you change them at will without breaking binary compatibility. One of them is the private class:

class FooPrivate;
class Foo {
public:
Foo();
private:
FooPrivate *d;
};


A technique that is used in Qt and KDE each class has a private class (with its own header). Data, implementation and private functions are moved into the private object. This provides a number of very positive benefits.
- You can add internal virtual functions, change function names, and generally do whatever you want.
- The implemenation can be completly changed without worrying about breaking binary compatibility.
- Users of the class don't need to include all the headers for private objects, reducing compile time.

3 comments:

André said...

A serious drawback of these private classes is that you can easily get stuck if you want to change the behaviour of a class slightly by subclassing. Doing that is very C++, but this technique makes that a nightmare as you can't touch the private subclass that does most of the work from the subclass.

Benjamin Meyer said...

Well the though would be that anything in the private class would be private in the class and so not accessible from a subclass either.

Kazade said...

I wouldn't say it would be a drawback that the subclasses can't access the private instance of the implementation class. It increases encapsulation to make all member variables private in any class.

If a subclass needs access to the FooPrivate instance then you can created a protected member function(s) to access what you need. This still limits routes of access to the member variable. Making the member protected is almost as un-encapsulated as making it public, there would still be a unlimited number of classes that *could* access the FooPrivate pointer directly.

One improvement to the design that I would suggest is to use a shared_ptr to handle the pointer-to-implementation. But of course that's nothing to do with encapsulation it just frees you from the burden of making sure that the memory allocated to the implementation instance is released.

Popular Posts