是C 库中必需的基于预处理器的功能开关
Are preprocessor-based feature switches necessary in C++ libraries?
我一直在研究一些成熟的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
因此,以最小的形式,运行时有一个分支,最有可能以功能指针的形式。我同意这是一个最小的开销,但这是不必要的。第二个最值得注意的区别是语义,因为一个发生在编译时,另一个发生在运行时,传达的意图是不同的,在第一种情况下,您基本上是告诉人们您的功能您可以激活或不适合程序。,在第二种情况下,您要告诉人们您有多种处理某些东西的方法,可以选择一种方法而不是其他方法。
总的来说,如果您将这些差异视为较小的差异,则可以选择一个感觉越舒适的差异,因为每个选择都有其小缺陷(宏定义具有粗略的预处理行为,并添加了新的语言水平,策略模式具有不必要的开销等。
策略模式在定义上是运行时算法。另一方面,预处理器功能是编译时间。这意味着,它们在编译时解决了,并且在编译后,它们不再存在了。我不确定您是否知道这一点,但我会说您是。因此,您的问题减少到:为什么我需要在编译时需要预处理器功能?
好吧,除了明显的多样性理由之外,考虑了您的策略模式涉及两个不同库的情况。现在,如果您使用预处理器命令,则不必链接到程序不需要的分支。如果您使用策略模式,则必须链接所有内容!链接到您不需要的东西只是不良风格。由于平台或许可或其他限制,它也可能是由于各种原因而可行的(如塞巴斯蒂安在评论中提到的。
- 在功能块中使用新运算符时存在于堆或堆栈上?
- 是否有任何模式等效于虚拟模板功能?
- 预处理的 C/C++ 文件是否特定于计算机?
- 预处理器是否可以更改运算符重载功能的符号?
- C++中用于结构的纯数组的类似于TableView/DataFrame的通用功能
- 具有特定于枚举的实现和共享功能 (CRTP) 的类模板
- 预处理器 IDE 是否仅具有功能?
- Java等效于C Botan功能调用
- 仅当元组中存在该类型时,将功能应用于元组元素
- 尾部调用优化是否适用于此功能?
- 功能类似于 Perl 在 C++ 中的 AUTOLOAD
- Vigenere密码的C++功能有时才起作用(适用于某些输入,跳过其他输入的班次)
- 从没有预处理器的HANA元组中创建功能签名
- C 的功能类似于Numpy Flatten
- 是C 库中必需的基于预处理器的功能开关
- DO模板声明仅适用于以前立即声明的功能
- 仅适用于单个功能的部分模板专业化
- 将相同的功能应用于不同大小的N数组的每个元素
- 是否有相当于设置的Linux功能
- 在这种情况下,(N)RVO是否适用于我的功能