使用内联命名空间的 API 版本控制

Posted

技术标签:

【中文标题】使用内联命名空间的 API 版本控制【英文标题】:API versioning with inline namespaces 【发布时间】:2020-08-03 23:36:36 【问题描述】:

我想对库 (seastar) 的 API 进行重大更改,但不会影响用户。所以我想为客户提供一种按照自己的节奏迁移到新 API 的方法。为此,我想使用内联命名空间。基本思想很简单,您为旧版本引入namespace v1,为新版本引入inline namespace v2(或相反)。这在https://foonathan.net/2018/11/inline-namespaces/ 中有很好的描述。当您想要引入另一个重大更改时,麻烦就开始了,namespace v3。让我们有一些示例代码作为进一步讨论的基础:

namespace v1 
    int foo(); // old version of foo


inline namespace v2 
    std::string foo(); // new, incompatible version of foo
    int bar(); // old version of bar


namespace v3 
    std::string bar(); // new, incompatible version of bar

现在如果我想将默认的 API 版本更新为 v3,即默认使用 v3 版本的bar(),我可以将namespace v3 设置为inline。我们遇到了困境:如果我只做 v3 inline 我会破坏我的客户,他们已经迁移到使用最新的v2 版本的foo()(因此正在使用它)没有命名空间限定符)。如果我同时创建v2v3 inline,以便可以在全局(库)命名空间中访问所有函数的最新版本,我会在v3::bar()v2:bar() 之间引入歧义。如果我将 v2::foo() 移动到 v3,我会破坏刚开始迁移到 v2::foo() 并使用完全限定名称 (::v2::foo()) 的客户。另一种选择是在与最新 API 版本相对应的命名空间中重新声明所有最新版本的函数,并仅生成 inline。这是很多重复和一些额外生成的代码。有没有更优雅的解决方案?

我还被建议只在inline namespace v3 中使用using v2::bar(等等)将所有符号的最新版本导出到最新的内联命名空间中。然而,据我所知,这会破坏 ADL。

【问题讨论】:

【参考方案1】:

我最终解决了一个没有我想要的那么漂亮和优雅的解决方案,但它很简单而且很有效。 我的解决方案是为每个重大更改引入两个新的 api 版本。就问题中的示例而言,我执行了以下操作:

namespace v1 
    int foo(); // old version of foo


inline namespace v2 
    std::string foo(); // new, incompatible version of foo

namespace v3 
    int bar(); // old version of bar


inline namespace v4 
    std::string bar(); // new, incompatible version of bar

奇数版本代表已更改符号的弃用版本,而偶数版本代表其新版本。这个系统是可扩展的、简单的和健壮的,但是它有点不直观并且绝对不优雅。 我有预处理器宏,允许客户端选择默认的“api 版本”。例如,如果客户端还没有准备好使用最新的 API,它可以选择 API 版本 3,之后命名空间 v2 和 v3 将内联。在他们迁移到最新的 API 后,他们可以将 API 版本提升到 4,这将导致上述状态。

【讨论】:

以上是关于使用内联命名空间的 API 版本控制的主要内容,如果未能解决你的问题,请参考以下文章

寻求对内联命名空间的澄清

C++ Primer 5th笔记(chap 18 大型程序工具)内联命名空间 (inline namespace)

为啥内联未命名的命名空间?

.Net 核心版本控制:文件夹结构/命名空间

封闭命名空间内联时的嵌套命名空间定义

内联命名空间中对命名空间的不明确引用