38

我正在为一个 C++ 库设计一个 API,它将分布在一个 dll / 共享对象中。该库包含具有虚函数的多态类。我担心如果我在 DLL API 上公开这些虚拟函数,我就无法使用更多的虚拟函数扩展相同的类,而不会破坏与为该库的先前版本构建的应用程序的二进制兼容性。

一种选择是使用PImpl习惯用法来隐藏所有具有虚函数的类,但这似乎也有其局限性:这样应用程序就失去了对库的类进行子类化和覆盖虚方法的可能性。

您将如何设计一个可以在应用程序中子类化的 API 类,同时又不会失去在新版本的 dll 中使用(非抽象)虚拟方法扩展 API 的可能性,同时保持向后二进制兼容?

更新:库的目标平台是 windows/msvc 和 linux/gcc。

4

5 回答 5

36

几个月前,我写了一篇名为“在 GNU/Linux 系统上用 C++ 实现的共享库的二进制兼容性”[ pdf ] 的文章。虽然 Windows 系统上的概念相似,但我确信它们并不完全相同。但是在阅读了这篇文章后,您可以了解与兼容性有关的 C++ 二进制级别正在发生的事情。

顺便说一句,GCC 应用程序二进制接口在标准文档草案“ Itanium ABI ”中进行了总结,因此您将有一个正式的基础来选择您选择的编码标准。

举个简单的例子:在 GCC 中,如果没有其他类继承它,您可以使用更多虚函数扩展一个类。阅读文章以获得更好的规则集。

但无论如何,规则有时过于复杂而难以理解。因此,您可能对验证两个给定版本兼容性的工具感兴趣: Linux的 abi-compliance-checker

于 2009-11-21T10:13:45.937 回答
11

KDE 知识库上有一篇有趣的文章,描述了在编写库时针对二进制兼容性的注意事项:Policies/Binary Compatibility Issues With C++

于 2009-11-21T12:56:27.427 回答
1

C++ 二进制兼容通常很困难,即使没有继承。以 GCC 为例。在过去的 10 年中,我不确定他们有多少破坏性的 ABI 更改。然后 MSVC 有一组不同的约定,因此无法使用 GCC 链接到它,反之亦然……如果将其与 C 世界进行比较,编译器互操作似乎更好一些。

如果你在 Windows 上,你应该看看 COM。当您引入新功能时,您可以添加接口。然后调用者可以QueryInterface()让新的调用者公开新的功能,即使你最终改变了很多东西,你也可以把旧的实现留在那里,或者你可以为旧的接口编写 shims。

于 2009-11-21T08:56:00.727 回答
1

我认为您误解了子类化的问题。

这是你的粉刺:

// .h
class Derived
{
public:
  virtual void test1();
  virtual void test2();
private;
  Impl* m_impl;
};

// .cpp
struct Impl: public Base
{
  virtual void test1(); // override Base::test1()
  virtual void test2(); // override Base::test2()

  // data members
};

void Derived::test1() { m_impl->test1(); }
void Derived::test2() { m_impl->test2(); }

看 ?覆盖 的虚拟方法没有问题Base,您只需要确保重新声明它们virtualDerived以便那些从 Derived 派生的人知道他们也可以重写它们(仅当您愿意时,顺便说一句,这是提供finalfor那些缺少它的人),您仍然可以为自己重新定义它,Impl甚至可以调用该Base版本。

那里没有问题Pimpl

另一方面,你失去了多态性,这可能很麻烦。由您决定是否需要多态性或只是组合。

于 2009-11-21T18:49:12.943 回答
-1

如果在头文件中公开 PImpl 类,则可以从它继承。您仍然可以保持向后可移植性,因为外部类包含指向 PImpl 对象的指针。当然,如果库的客户端代码不是很聪明,它可能会滥用这个暴露的 PImpl 对象,并破坏二进制的向后兼容性。您可以在 PImpl 的头文件中添加一些注释来警告用户。

于 2014-04-23T09:01:08.113 回答