问题
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