ABI Stability of C++ Libraries

ABI Stability Is Difficult

It’s easy to break the ABI of a C++ class. Just add a new member variable to a public class or add a member variable to an implementation class that’s used a a member instance in a public class. C has the same problem, though it’s not quite as bad because they tend to use pointers more rather than member instances.

I have mostly had the luxury of working in these two situations:

  • The code is used by a closed group of people, who expect to rebuild their entire set of software often.
  • The code is open source, even during its development, so the API and ABI can become quite mature before it is declared ABI stable. However, there are still things that I wish I could change now.

But what if it’s a toolkit whose first public version will be released as ABI-stable?

Pimpl?

Pimpl is the only real solution that I know of. You use an opaque pointer as a member variable. The class to which the pointer points is not fully defined in the header. For instance:

class PrivateThing;

class Something
{
   ...

private:
  PrivateThing* m_priv;
};

The instance of PrivateThing is then created in the Something constructor.

This has some disadvantages:

  • It requires more runtime allocation of memory, in more pieces, which is slow.
    However, I wonder whether this is significant in the average application, given that the public class is probably a member of something that’s allocated dynamically anyway.
  • It causes a slight slowdown, because an extra dereference is necessary whenever the member data is accessed.
    This might not be significant.
  • It makes the code less easy to read, and adds repetitive code to each class method.
  • You cannot provide protected (rather than private) members for use in derived classes while simultaneously hiding those members.
    Many would say this is a good thing. I would probably make m_priv protected though, so that derived classes in the toolkit itself can access members directly, with their knowledge of its definition, while preventing use from applications.

I am fairly confident that this is the best solution, particularly as it’s equivalent to what GTK+ does in C. But please do comment if you have opinions or alternatives.

18 thoughts on “ABI Stability of C++ Libraries

  1. I think the QT4 libraries are a good example of how to implement pimpl in an elegant way. Besides some macros to make life easier (Q_DECLARE_PRIVATE, Q_DECLARE_PUBLIC, Q_D, Q_Q), the way they implement it allows derived classes to also have a derived version of the private class (look at e.g., QPushButton, which has a private data class QPushButtonPrivate that derives from QAbstractButtonPrivate), which means that there is only one private allocation per instantiation even for derived objects (they use a protected constructor QAbstractButton (QAbstractButtonPrivate &dd, …) to get the allocation of the right class type). Hope this helps a bit.

  2. One suggestion: make all your classes abstract, then implement them as subclasses. That way, you can separate the interface and the implementation.

    Alternatively, just find a better language :-)

  3. The KDE4 core libraries all use that technique to ensure API/ABI compatibility. Check out planet kde, someone talked about it recently.

  4. IMHO this code looks really ugly. I would rather either prefer the solution with abstract classes or to simply sacrifice ABI stability in favour of code quality. I we do it the way C does we could rather use C.

    Gtkmm has a stable API since 2.4 so is this really a major problem?

  5. If you can control the allocator, the private structure doesn’t need to be a separate allocation. The system used in GTK allocates all private structures at the end of the instance. So if you have a class “A” with a private struct “a”, and a class “B” subclassed from A, then instances of A are laid out as “Aa” while instances of B are laid out as “ABa” (hopefully that’s clear enough). You still pay for the extra pointer, but that is not too bad when compared with multiple allocations.

  6. I wonder how significant the cost of “multiple allocations” is when those “priv” instances are allocated using a slab allocator like glice, compared to the g_type_instance_get_private API of GType.

    But then again, this only sounds important for things that are quantitative (needed a lot). For the header type of a e-mail client/library that I’ve been working on, I simply decided to use an all-opaque type and a forward typedef (the struct in the .c file, and typedef in the .h file) to avoid any second allocation. Mostly because a second allocation takes time (malloc is slow). With gslice, however, both the heap-admin and the speed loss isn’t going to be really that significant, I think.

    The last reason why I still did it (no private allocation, but rather making the entire type opaque) was avoiding the 4 bytes of a pointer that would be needed for a “priv” variable, versus the speed of the g_type_instance_get_private API (when sorting, the dereferencing would happen a lot).

    What about putting the private members of a C++ class to the bottom of the type’s description (in the .h file)? Does this influence how the compiler iterpretes it (like alignment and things like that)? I don’t know whether it makes a difference, but in C I usually put the members that are least likely going to be changed above the members that are more likely going to be changed.

  7. pvanhoof, just as in a C struct, changing the data members of a C++ class changes the size of the instance, which breaks ABI, particularly for other classes that inherit from it.

  8. I have been pondering this issue for libcrossmark ( http://www.abisource.com/projects/libcrossmark/ ). What about installing only headers for abstract base classes and factories to create them? That would allow for subclassing flexibility inside your own project.
    OTOH it’s certainly a bit tedious not being able to simply use ‘new’ for instantiation, and in some cases objects on the stack are convenient.

  9. # It causes a slight slowdown, because an extra dereference is necessary whenever the member data is accessed.
    This might not be significant.

    I read that this is not even measurable within the scope of most applications.

    # It makes the code less easy to read, and adds repetitive code to each class method.

    It makes the interface to the code (the header) more easy to read.

    # You cannot provide protected (rather than private) members for use in derived classes while simultaneously hiding those members.
    Many would say this is a good thing. I would probably make m_priv protected though, so that derived classes in the toolkit itself can access members directly, with their knowledge of its definition, while preventing use from applications.

    You could also you can just add protected assessors if you want certain members available to derived classes in general.

  10. Even for unstable APIs/ABIs, I have used this method simply to reduce recompilations (without this technique, adding a private data member to a class requires all other users of that class to be recompiled; while this makes sense knowing the technical details it’s somewhat of an annoying oddity given that the private member was marked private in order to keep it “hidden” from the users of the class).

    Additionally, I agree with Patrick that this technique increases code readability for the majority of users (namely the users of the class), at the expense (as you point out) of the few (namely, the implementors and maintainers of the class).

    (I haven’t had to worry too much about the private vs. protected thing in conjunction with this opaque pointer technique. Perhaps having two opaque pointers, one private and one protected would do the trick though? Either that, or use Patrick’s suggestion of protected accessors.)

  11. > I would probably make m_priv protected though, so that derived classes in the toolkit itself
    > can access members directly, with their knowledge of its definition, while preventing use from applications.

    If classes from inside the toolkit can derive from your class, then classes from outside can do so as well. Which means, if you make m_priv protected instead of private and afterwards change something with the private class, you’re breaking binary compatibility _again_. Which kind of defeats the whole purpose of the Pimpl idiom. Of course, you can say that applications from outside should know better in this case, but 1. they will do it anyways, and 2. if the classes inside your toolkit need to access some of that stuff, it’s quite likely that the ones from outside also need it, so it should be exposed anyways.

    I get your reasoning, but you cannot have a stable ABI and a protected PrivateThing member at the same time.

  12. Jakob Petsovits wrote:
    > If classes from inside the toolkit can derive from your class, then classes from outside can do so as well. Which means, if you make m_priv
    > protected instead of private and afterwards change something with the private class, you’re breaking binary compatibility _again_.

    Not if I don’t install the header for the private class. All the non-toolkit classes would have is an opaque pointer.

  13. pvanhoof: By creating the instance struct and private struct in one allocation, they will be contiguous in memory. As well as saving the allocation, this is good VM-wise because the two structs are likely to be in the same page.

  14. fraggle, the interface – inplementation method has been used by some third party libraries that I use. However, the downside of this method, is that you need a ‘GetMyClassImpl()’ method, as you can’t use the new operator on classes you don’t know the size(, etc) of. So you’d have MyClass instance = GetMyClassImpl();. Or do you know a better way?

  15. There’s the classic Fragile Base Class paper written by one of the ex-BeOS engineers, which was a truly shining example of an operating system written in C++. It has a number of good tips to retain ABI compatibility.

    On Mac OS X, Apple tend to pad a lot of their normal C and Objective-C structures with 4-8 extra ‘reserved’ members, to give them room to add more data later. I think this works fine.

    I’d be more concerned about (1) ABI compatibility for inline functions and (2) deciding add a virtual function later if you don’t currently have any. I usually have quite a few small member functions defined inline in the header file for speed reasons (since they’re hit very often from the outside the class). Unfortunately inlining the accessors this way greatly reduces your chances for ABI compatbility.

    Congratulations on the gtkmm API, by the way. I haven’t used it much, but from what I’ve seen of it so far, it’s one of the best C++ APIs I’ve seen. Voluntarily using gtk+ after looking at gtkmm is sheer lunacy.

Comments are closed.