是C 库中必需的基于预处理器的功能开关

Are preprocessor-based feature switches necessary in C++ libraries?

本文关键字:于预 功能 开关 处理器      更新时间:2023-10-16

我一直在研究一些成熟的C 项目,我注意到一种模式,该模式使用预处理标志在编译时启用功能。

例如:

#ifdef MY_WIDGET
Widget createMyWidget() {
    // etc... 
}
#endif

然后在代码中的其他地方:

#ifdef MY_WIDGET
widgets.push_back(createMyWidget());
// etc... 
#endif

对我来说,这似乎是不必要的,因为我们可以使用继承或std::function采用策略模式。

当前用户的应用程序可能看起来像这样(其中库已与-DMY_WIDGET编译):

#include <library/startApp.hpp>
int main() {
  startApp(); // createMyWidget will be called by the library
  return 0;
}

但是,我们可以重新设计库,以便用户可以编写以下内容:

#include <library/startApp.hpp>
#include <my-widget-plugin/createMyWidget.hpp>
int main() {
  const std::vector<Widget> widgets = { createMyWidget() };
  startApp(widgets);
  return 0;
}

现在,库没有任何编译开关,这使得构建更简单,但是我们仍然可以扩展库的功能。如果有人在没有功能的情况下编译库但随后尝试错误地使用该功能的情况下,这也可以防止潜在的混乱。

适当使用const允许在任何一种情况下都可以效率地编译二进制。如果小部件应该懒惰地实例化,我们可以通过工厂的媒介。

基于预处理器的功能开关只是该策略模式的不太控制版本吗?

主要区别在于选择。

预编译器标志是您在启动程序之前在编译时做的选择以后的时间。

然后我们有:

策略模式(也称为策略模式)是一种行为软件设计模式,可以在运行时选择算法的行为。

wikipedia

因此,以最小的形式,运行时有一个分支,最有可能以功能指针的形式。我同意这是一个最小的开销,但这是不必要的。第二个最值得注意的区别是语义,因为一个发生在编译时,另一个发生在运行时,传达的意图是不同的,在第一种情况下,您基本上是告诉人们您的功能您可以激活或不适合程序。,在第二种情况下,您要告诉人们您有多种处理某些东西的方法,可以选择一种方法而不是其他方法。


总的来说,如果您将这些差异视为较小的差异,则可以选择一个感觉越舒适的差异,因为每个选择都有其小缺陷(宏定义具有粗略的预处理行为,并添加了新的语言水平,策略模式具有不必要的开销等。

策略模式在定义上是运行时算法。另一方面,预处理器功能是编译时间。这意味着,它们在编译时解决了,并且在编译后,它们不再存在了。我不确定您是否知道这一点,但我会说您是。因此,您的问题减少到:为什么我需要在编译时需要预处理器功能?

好吧,除了明显的多样性理由之外,考虑了您的策略模式涉及两个不同库的情况。现在,如果您使用预处理器命令,则不必链接到程序不需要的分支。如果您使用策略模式,则必须链接所有内容!链接到您不需要的东西只是不良风格。由于平台或许可或其他限制,它也可能是由于各种原因而可行的(如塞巴斯蒂安在评论中提到的。