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

I am 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_;
};

Virtual methods are reserved#not overridden in client code and are not called anywhere. They serve as placeholders for future expansion. Let's say I replace one of the reserved methods with a virtual function with a 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_;
};

, , vtable . , reserved1(), , , - foo4. , - ODR. , reserved1, foo4?

+4
3

reserved1 vtable, -, .

, : , , , .

? , lib (, MyClass::PImpl).

+2

, , , vtable ; vtable , - .

: , vtable , , .

:

// Class:

class MyClass {
public:
    //public interface

private:

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

    class Impl;
    Impl* impl_;
};

// current vtable:

+-------------------+
| foo1(int)         | < offset 0
| foo2(int, bool)   | < offset 1
| foo3(double)      | < offset 2
+-------------------+

// code is compiled and references offsets 1 and 2 in the vtable

// then you change the class with an added method:

class MyClass {
public:
    //public interface

private:

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

    class Impl;
    Impl* impl_;
};

// New vtable:

+-------------------+
| foo1(int)         | < offset 0
| foo2(int, bool)   | < offset 1
| foo3(double)      | < offset 2
| foo4(int, int)    | < offset 3
+-------------------+

// the offsets of the first three are the same so the old code
// that was compiled to use offsets 1 and 2 still works
+2

, factory , :

    class MyClass {
    public:
        MyClass(...);
        ...
    };

    class MyClass {
    public:
        MyClass* create(...);
        ...
    };

MyClass , (GCC) . v- , foo4.

If you declare a factory method, then it will return a pointer to a class object with a new v-table for old clients so that they can find foo4and run your new code.

0
source

All Articles