C++ 继承和动态库

C++ Inheritance and dynamic libraries

思路如下。我有一个带有 class 的库版本 1,如下所示:

class MY_EXPORT MyClass
{
public:
    virtual void myMethod(int p1);
}

在版本 2 中,class 被修改为:

class MY_EXPORT MyClass
{
public:
    virtual void myMethod(int p1);
    virtual void myMethod2(int p1, int p2);
}

//implementation of myMethod2 in cpp file
void MyClass::myMethod2(int p1, int p2)
{
    myMethod(p1);
    //...
}

现在假设用户编译了库的第 1 版,并通过覆盖 myMethod 扩展了 MyClass。现在他将库更新到版本 2,无需 重新编译。让我们进一步假设动态链接器仍然成功找到库并加载它。

问题是,如果我在库中的某个地方调用方法 instance->myMethod2(1, 2);,它会起作用,还是应用程序会崩溃?在这两种情况下,class 没有成员,因此大小相同。

我认为猜测该应用程序是否会崩溃没有意义,行为未定义。必须重新编译应用程序,因为库中有 ABI 更改。

当库调用 instance->myMethod2(1, 2); 时,它必须通过在应用程序代码中创建的虚拟 table,假设只有一个虚拟方法:myMethod。从那时起,您将获得未定义的行为。简而言之,当库 ABI 更改时,您必须重新编译应用程序。

KDE C++ ABI guidelines明确禁止这样的改变。派生 类 的虚拟表将不包含新方法的地址,因此对派生 类 的对象的这些方法的虚拟调用将崩溃。

通过在不重新编译的情况下更改 class 的定义,您违反了 One Definition Rule。没有重新编译的用户使用的是旧定义,而您的库使用的是新定义。这会导致未定义的行为。

要了解这可能如何表现,请考虑使用 VTable 调度函数调用的虚拟函数的典型实现。库用户派生了一个class,这个派生的class在VTable中只有一个函数。如果指向此 class 的指针或引用被传递到库中,并且库尝试调用第二个函数,它将尝试访问不存在的 VTable 条目。这几乎总是会导致崩溃,尽管在涉及未定义行为时无法保证。