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

半腔热情 提交于 2019-12-05 02:54:34

问题


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?


回答1:


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).




回答2:


You don't need to do this because, as long as the order of the methods is not altered, you can add methods to the end of the vtable without changing the beginning; pointers are accessed in the vtable by their offset from the beginning, so adding something to the end won't affect anything.

This is the entire point of interface classes like this: you can pass pointers to derived classes, which have a vtable with extra methods on the end, to functions which expect a pointer to a base class.

For instance:

// 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



回答3:


You can use reserved virtual functions at end of class declaration, but your class should has an exported constructor or a factory method that can return a pointer to created class from the shared library side:

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

OR

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

Your sample class MyClass has no any declared constructor, so the compiler (GCC) will generate inline constructor for this class automatically. This constructor will create old v-table for old client applications, which has no entry for foo4.

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



来源:https://stackoverflow.com/questions/9352094/would-unused-private-virtual-methods-allow-future-expansion-without-breaking-abi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!