Search code examples
c++inheritancebinary-compatibility

Pure virtual functions and binary compatibility


Now, I know it is generally bad to add new virtual functions to non-leaf classes as it breaks binary compatibility for any derived classes which haven't been recompiled. However, I have a slightly different situation:

I have an interface class and implementation class compiled into a shared library, for example:

class Interface {
    public:
        static Interface* giveMeImplPtr();
        ...
        virtual void Foo( uint16_t arg ) = 0;
        ...
}

class Impl {
    public:
        ...
        void Foo( uint16_t arg );
        ....
}

My main application uses this shared library, and could basically be written as:

Interface* foo = Implementation::giveMeImplPtr();
foo->Foo( 0xff );

In other words, the application doesn't have any classes which derive from Interface, it merely uses it.

Now, say I want to overload Foo( uint16_t arg ) with Foo( uint32_t arg ), am I safe to do:

 class Interface {
    public:
        static Interface* giveMeImplPtr();
        ...
        virtual void Foo( uint16_t arg ) = 0;
        virtual void Foo( uint32_t arg ) = 0;
        ...
}

and recompile my shared library without having to recompile the application?

If so, are there any unusual caveats I need to be aware of? If not, do I have any other options other than to take the hit and up-version the library, thus breaking backwards compatibility?


Solution

  • ABI basically depends on the size and shape of the object, including the vtable. Adding a virtual function will definitely change the vtable, and how it changes depends on the compiler.

    Something else to consider in this case is that you're not just proposing an ABI breaking change, but an API breaking one that is very difficult to detect at compile time. If these were not virtual functions and ABI compatibility wasn't an issue, after your change, something like:

    void f(Interface * i) {
      i->Foo(1)
    }
    

    will quietly end up calling your new function, but only if that code is recompiled, which can make debugging very difficult.