如何设计一个 C++用于二进制兼容可扩展性的 API

发布于 2024-08-12 08:41:51 字数 423 浏览 13 评论 0原文

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

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

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

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

I am designing an API for a C++ library which will be distributed in a dll / shared object. The library contains polymorhic classes with virtual functions. I am concerned that if I expose these virtual functions on the DLL API, I cut myself from the possibility of extending the same classes with more virtual functions without breaking binary compatibility with applications built for the previous version of the library.

One option would be to use the PImpl idiom to hide all the classes having virtual functions, but that also seem to have it's limitations: this way applications lose the possibility of subclassing the classes of the library and overriding the virtual methods.

How would you design a API class which can be subclassed in an application, without losing the possibility to extend the API with (not abstract) virtual methods in a new version of the dll while staying backward binary compatible?

Update: the target platforms for the library are windows/msvc and linux/gcc.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

天赋异禀 2024-08-19 08:41:51

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

顺便说一句,GCC应用程序二进制接口在标准文档草案“中进行了总结Itanium ABI”,这样您就可以为您选择的编码标准奠定正式的基础。

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

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

Several months ago I wrote an article called "Binary Compatibility of Shared Libraries Implemented in C++ on GNU/Linux Systems" [pdf]. While concepts are similar on Windows system, I'm sure they're not exactly the same. But having read the article you can get a notion on what's going on at C++ binary level that has anything to do with compatibility.

By the way, GCC application binary interface is summarized in a standard document draft "Itanium ABI", so you'll have a formal ground for a coding standard you choose.

Just for a quick example: in GCC you can extend a class with more virtual functions, if no other class inherits it. Read the article for better set of rules.

But anyway, rules are sometimes way too complex to understand. So you might be interested in a tool that verifies compatibility of two given versions: abi-compliance-checker for Linux.

吃不饱 2024-08-19 08:41:51

KDE 知识库上有一篇有趣的文章,描述了编写库时以二进制兼容性为目标的注意事项: C++ 的策略/二进制兼容性问题

There is an interesting article on the KDE knowledge base that describes the do's and don'ts when aiming at binary compatibility when writing a library: Policies/Binary Compatibility Issues With C++

水溶 2024-08-19 08:41:51

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

如果您使用的是 Windows,则应该查看 COM。当您引入新功能时,您可以添加接口。然后调用者可以使用 QueryInterface() 来让新的实现公开新功能,即使您最终改变了很多东西,您也可以保留旧的实现,或者为旧的实现编写垫片接口。

C++ binary compat is generally difficult, even without inheritance. Look at GCC for example. In the last 10 years, I'm not sure how many breaking ABI changes they've had. Then MSVC has a different set of conventions, so linking to that with GCC and vice versa can't be done... If you compare this to the C world, compiler inter-op seems a bit better there.

If you're on Windows you should look at COM. As you introduce new functionality you can add interfaces. Then callers can QueryInterface() for the new one to expose that new functionality, and even if you end up changing things a lot, you can either leave the old implementation there or you can write shims for the old interfaces.

百善笑为先 2024-08-19 08:41:51

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

这是你的 Pimpl:

// .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 的虚拟方法没有问题,您只需要确保在 Derived 中重新声明它们 virtual ,以便那些从 Derived 派生的人知道他们也可以重写它们(仅当您愿意时,顺便说一下,这是为那些缺乏它的人提供 final 的好方法),并且您仍然可以在 中为自己重新定义它Impl 甚至可以调用 Base 版本。

那里的 Pimpl 没有问题。

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

I think you misunderstand the problem of subclassing.

Here is your Pimpl:

// .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(); }

See ? No problem with overriding the virtual methods of Base, you just need to make sure to redeclare them virtual in Derived so that those deriving from Derived know they may rewrite them too (only if you wish so, which by the way is a great way of providing a final for those who lack it), and you may still redefine it for yourself in Impl which may even call the Base version.

There is no problem with Pimpl there.

On the other hand, you lose polymorphism, which may be troublesome. It's up to you to decide whether you want polymorphism or just composition.

守望孤独 2024-08-19 08:41:51

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

If you expose the PImpl class in a header file, then you can inherit from it. You can still maintain backward portability since the external classes contains a pointer to the PImpl object. Of course if the client code of the library isn't very wise, it could misuse this exposed PImpl object, and ruin the binary backward compatibility. You may add some notes to warn the user in the PImpl's header file.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文