C++ API Rules

If you find yourself building a C++ API or SDK for use by third party developers, a whole host of concerns will enter into the equation that are not part of building a usual C++ application. If you ship the source for everything, and the developer builds everyting into one large, statically linked application, your code looks a lot like the application code, and most of these concerns go away, but if you want to ship pre-built libraries, or even DLLs, then here are some rules to live by.

I've found that making your data members public in interfaces is very seldom a good idea. Instead, I've found that it helps structure code to divide things into three kinds:

  • Real components.
    These are made by calling factories or global factory functions. You talk to them through interfaces, that are pure virtual abstract base classes. There exists one or more implementation of each of these interfaces. The interfaces may accept "client" interfaces, which is how you describe yourself to the component. IVertexBuffer is a component of this kind. Crucial is that "operator==()" works on the pointer values, not the pointed-at class data, i e you don't define your own, and always reference these guys through pointers (or references had by de-referencing pointers).
    Typically, these interfaces have protected destructors, and use reference counting, or a separate "dispose()" method. The constructor is always protected (and usually inline, empty), because you only make instances of derived classes.
  • Value classes.
    These have "operator=()" and possibly public members, but no virtual functions. They are structs with smarts. They do not use dynamically allocated memory or complex members. Matrix, Vector and FileName are of this kind (if FileName is a fixed-size name with internal data storage).
    The underlying data a value class works on MAY be something like a handle returned by another API, that internally uses dynamic memory allocation (HWND, say :-); they key is that the object itself does not.
    Crucial is that "operator==()" works on the pointed-at data. Often, you'll want to implement these inline. If there's a lot of code to some member functions, you can out-of-line them.
  • Helper classes.
    These are something like value classes, but are often implemented as templates. They can use dynamically allocated memory. std::list<>, std::string<>, and CComPtr<> all are of this kind.
    Because these objects use memory management, there may be a mis- match if part of the object is in one linkage unit (DLL) and part is in another, so you have to never pass one of these guys to or from a function or interface that lives in another linkage unit. This means that the user is free to use these, but you should not yourself take these as arguments (i e, no std::string const & for name arguments!)
    A corollary is that Helper classes should not define virtual functions.

The temptation to put objects of kind 3 (helper classes) into DLLs may at times be strong. The problem is that, if you override global operator new (for example), that will not override the operator new used by the DLL on Windows, so you end up with mis-matched allocations. The Microsoft STL for Visual C++ version 6.0 had a library called MSVCPRT.DLL which had exactly this problem, and it was a pain to work with!
UNIX dynamic linkage works somewhat differently, in that one operator new will "win" in case there are multiple available at load time; the symbol tables are effectively merged. The problem with that is that you can't know, when building your code, what operator new will actually be used for memory allocation at run time! In fact, if you use dlopen() to open plug-ins, and they dynamically link against other shared libraries, it may even change at run-time!
If you are not scared by this, then that shows you just haven't been bitten yet. You will -- so try to use these simple design rules in your C++ API, to make it sting less.

I'm currently busy constructing my third large C++ API, over the course of ten years, so these lessons are the fruit of hard-earned experience :-)