Search code examples
c++virtualbackwards-compatibility

Would unused private virtual methods allow future expansion without breaking ABI compatibility?


I'm developing a shared library. Let's say I have the following class definition:

class MyClass {
public:
    //public interface

private:

    virtual void foo1(int);
    virtual void foo2(int, bool);
    virtual void foo3(double);

    virtual void reserved1();
    virtual void reserved2();
    virtual void reserved3();

    class Impl;
    Impl* impl_;
};

The reserved# virtual methods are not overridden in the client code and not called from anywhere. They serve as placeholders for future expansion. Let's say I replace one of the reserved methods with a virtual function with different signature and implementation:

class MyClass {
public:
    //public interface

private:

    virtual void foo1(int);
    virtual void foo2(int, bool);
    virtual void foo3(double);
    virtual void foo4(int, int);

    virtual void reserved2();
    virtual void reserved3();

    class Impl;
    Impl* impl_;
};

It would seem that it achieves full binary compatibility in this way, since the layout of the vtable doesn't change. The problem is that the old code would still ask the dynamic linker to resolve reserved1() and if the definition is not within the library, then the code would crash at link-time, or run-time if someone calls foo4. I assume this issue can't be solved portably, because of ODR. Maybe there's a way to trick the compiler to generate symbol of reserved1 that would act as an alias to foo4?


Solution

  • Since the function reserved1 is only there to preserve vtable layout compatibility, presumably nothing in the client code will call it.

    If it isn't called client code doesn't need any linker reference to it: this is obviously all platform-specific, but in general your scheme should work fine.

    Are the virtual methods really private though? If they can't be called or overridden from the client, you could just expose an opaque forward declaration and keep the implementation entirely inside your dynamic lib (eg, MyClass::PImpl).