当工件是库且标志影响 C 或 C++ 标头时,功能标志/切换

eta*_*ion 6 c c++ architecture software-design featuretoggle

关于功能标志/切换以及为什么要使用它们存在相当多的讨论,但大多数关于实现它们的讨论都围绕(网络或客户端)应用程序进行。如果您的产品/工件是 C 或 C++ 库,并且您的公共标头受标志影响,您将如何实现它们?

“天真的”做法并没有真正起作用:

/// Does something
/**
 * Does something really cool
#ifdef FEATURE_FOO
 * @param fooParam describe param for foo
#endif
 */
void doSomethingCool(
#ifdef FEATURE_FOO
    int fooParam = 42
#endif
);
Run Code Online (Sandbox Code Playgroud)

您不会想运送这样的东西。

  • 您发布的库是针对特定功能标志组合构建的,客户端不需要#define相同的功能标志来使事情正常工作
  • 你的公共标头中的 ifdef 很难看
  • 最重要的是,如果您禁用标志,您不希望客户看到有关禁用功能的任何内容- 也许这是即将推出的内容,并且您不想在准备好之前展示您的内容

在文件上运行预处理器来获取要分发的标头实际上并不起作用,因为这不仅会作用于功能标志,还会执行预处理器执行的所有其他操作。

没有这些缺陷的技术解决方案是什么?

Han*_*ant 2

由于版本控制,这种粘性最终会出现在代码库中。话题广泛,但很少有令人满意的答案。但你当然希望避免让事情变得更加困难。专注于您想要提供的兼容性类型。

仅当您需要二进制兼容性时才需要代码片段中建议的语法。它使库与客户端代码中的 doSomethingCool() 调用(不传递参数)兼容,而无需编译该客户端代码。换句话说,客户端程序员除了复制更新的 .dll 或 .so 文件之外什么都不做,不需要任何更新的标头,并且正确获取功能标志完全是您的负担。二进制兼容性很难可靠地实现,除了标志争论之外,很容易犯错误。

但您实际上谈论的是源兼容性,您确实为用户提供了更新的标头,并且他重建了代码以使用库更新。在这种情况下,您不需要功能标志,C++ 编译器本身会确保传递一个参数,它将是 42。无论是您端还是用户端,根本不需要标志。

另一种方法是提供重载。换句话说,doSomethingCool() 和 doSomethingCool(int) 函数。客户端程序员继续使用原始重载,直到他准备好继续前进。当函数体必须改变太多时,您也喜欢重载。如果这些功能不是虚拟的,那么它甚至提供链接兼容性,在某些特定情况下可能很有用。不需要功能标志。