C ++库中是否需要基于预处理器的功能开关?

时间:2017-03-07 09:33:36

标签: c++ design-patterns preprocessor

我一直在研究一些成熟的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允许编译的二进制文件在任何一种情况下都同样有效。如果小部件应该懒惰地实例化,我们可以传递一个工厂向量。

基于预处理器的功能切换只是策略模式的控制较少的版本吗?

2 个答案:

答案 0 :(得分:2)

主要区别在于选择的时间。

预编译器标志是你在编译之前做的选择,你甚至可以在启动你的程序之前做一次,然后你可以有多个版本的程序,每个版本都做一些具体的事情,而不是其他任何其他替代方案都不能打败性能。以后。

然后我们有:

  

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

     

维基百科

因此,在最小的形式中,在运行时有一个分支,很可能是函数指针的形式。我同意这是一个最小的开销,但它是一个不必要的开销。 第二个最显着的区别是语义,因为一个发生在编译时,另一个发生在运行时,传达的意图是不一样的,在第一种情况下,你基本上告诉人们你有你可以为你的程序激活或不激活的功能,在第二种情况下,你告诉人们你有多种处理方式,而且可以选择一种方式而不是其他方式。

总而言之,如果你认为差异是次要的,那么你可以选择一个感觉更加舒适的差异,因为每个选择都有其小的缺陷(宏定义具有粗略的预处理器行为并添加新的语言级别,策略模式有不必要的开销等),但如果两者都适合你,那么当你可以在编译时选择并使用策略能够在运行时选择时使用定义。

答案 1 :(得分:1)

策略模式根据定义是运行时算法。另一方面,预处理器功能是编译时的。这意味着,它们在您编译时已经解决,并且在编译之后,它们不再存在。我不确定你是否意识到这一点,但我会说你是。所以你的问题简化为:为什么我在编译时需要预处理器功能?

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