6

我有三个密切相关的应用程序,它们是从相同的源代码构建的——比如说 APP_A、APP_B 和 APP_C。APP_C 是 APP_B 的超集,而 APP_B 又是 APP_A 的超集。

到目前为止,我一直在使用预处理器定义来指定正在构建的应用程序,它的工作原理是这样的。

// File: app_defines.h
#define APP_A 0
#define APP_B 1
#define APP_C 2

然后我的 IDE 构建选项指定(例如)

#define APPLICATION APP_B

...在源代码中,我会有类似的东西

#include "app_defines.h"

#if APPLICATION >= APP_B
// extra features for APPB and APP_C
#endif

但是,今天早上我开枪打死了自己,只是从一个文件中省略了 #include "app_defines.h" 行,这浪费了很多时间。一切编译正常,但应用程序在启动时与 AV 一起崩溃。

我想知道处理这个问题的更好方法是什么。以前,这通常是我认为可以使用#define 的少数几次之一(无论如何,在 C++ 中),但我仍然搞砸了,编译器没有保护我。

4

10 回答 10

12

您不必总是在共享公共代码库的应用程序中强制继承关系。真的。

有一个古老的 UNIX 技巧,您可以根据 argv[0](即应用程序名称)来定制应用程序的行为。如果我没记错的话(我看过它已经 20 年了),rsh 和 rlogin 是/是同一个命令。您只需根据 argv[0] 的值进行运行时配置。

如果您想坚持构建配置,这是通常使用的模式。您的构建系统/makefile 在命令上定义了一个符号,例如 APP_CONFIG 是一个非零值,那么您就有一个带有配置螺母和螺栓的通用包含文件。

#define APP_A 1
#define APP_B 2

#ifndef APP_CONFIG
#error "APP_CONFIG needs to be set
#endif

#if APP_CONFIG == APP_A
#define APP_CONFIG_DEFINED
// other defines
#endif

#if APP_CONFIG == APP_B
#define APP_CONFIG_DEFINED
// other defines
#endif

#ifndef APP_CONFIG_DEFINED
#error "Undefined configuration"
#endif

此模式强制配置是命令行定义的并且是有效的。

于 2008-11-04T14:20:49.767 回答
7

您正在尝试做的似乎与“产品线”非常相似。Carnigie Melon 大学在此模式上有一个很好的页面:http ://www.sei.cmu.edu/productlines/

这基本上是一种构建具有不同功能的软件的不同版本的方法。如果您想像 Quicken Home/Pro/Business 之类的东西,那么您就走上了正轨。

虽然这可能不是您所尝试的,但这些技术应该会有所帮助。

于 2008-11-04T13:27:51.043 回答
2

在我看来,您可能会考虑将代码模块化为单独编译的元素,从公共模块的选择和特定于变体的顶级(主)模块中构建变体。

然后控制哪些部分进入构建,哪些头文件用于编译顶层,哪些 .obj 文件包含在链接器阶段。

一开始你可能会觉得这有点困难。从长远来看,您应该有一个更可靠和可验证的构建和维护过程。您还应该能够进行更好的测试,而不必担心所有 #if 变化。

我希望您的应用程序还不是非常大,并且解开其功能的模块化不必处理一大堆泥巴。

在某些时候,您可能需要运行时检查以验证构建使用的组件是否与您想要的应用程序配置一致,但这可以稍后解决。您还可以实现一些编译时一致性检查,但您将通过头文件和进入特定组合的从属模块的入口点签名获得大部分检查。

无论您是使用 C++ 类还是在 C/C++ 公共语言级别上进行操作,这都是相同的游戏。

于 2008-11-04T22:19:11.893 回答
1

做这样的事情:


CommonApp   +-----   AppExtender                        + = containment
                      ^    ^    ^
                      |    |    |                       ^ = ineritance
                    AppA  AppB  AppC                    |

将您的通用代码放在 CommonApp 类中,并在战略位置调用接口“AppExtender”。例如,AppExtender 接口将具有 afterStartup、afterConfigurationRead、beforeExit、getWindowTitle 等函数...

然后在每个应用程序的 main 中,创建正确的扩展程序并将其传递给 CommonApp:


    --- main_a.cpp

    CommonApp application;
    AppA appA;
    application.setExtender(&appA);
    application.run();

    --- main_a.cpp

    CommonApp application;
    AppB appB;
    application.setExtender(&appB);
    application.run();
于 2008-11-04T13:50:44.633 回答
1

但是,今天早上我开枪打死了自己,只是从一个文件中省略了 #include "app_defines.h" 行,这浪费了很多时间。一切编译正常,但应用程序在启动时与 AV 一起崩溃。

这个问题有一个简单的解决方法,打开警告,这样如果 APP_B 没有定义,那么你的项目就不能编译(或者至少产生足够的警告,让你知道有问题)。

于 2008-11-04T22:05:31.357 回答
1

问题是使用带有未定义名称的#if 指令就好像它被定义为0。这可以通过始终先执行#ifdef 来避免,但这既麻烦又容易出错。

稍微好一点的方法是使用命名空间和命名空间别名。

例如

namespace AppA {
     // application A specific
}

namespace AppB {
    // application B specific
}

并使用你 app_defines.h 来做命名空间别名

#if compiler_option_for_appA
     namespace Application = AppA;
#elif compiler_option_for_appB
     namespace Application = AppB;
#endif

或者,如果组合更复杂,命名空间嵌套

namespace Application
{
  #if compiler_option_for_appA
     using namespace AppA;
  #elif compiler_option_for_appB
     using namespace AppB;
  #endif
}

或以上任意组合。

优点是,当您忘记标头时,您会从编译器 iso 中得到未知的命名空间错误,因为 APPLICATION 默认为 0。

话虽如此,我也遇到过类似的情况,我选择将所有内容重构为许多库,其中绝大多数是共享代码,并让版本控制系统处理依赖定义等的不同应用程序 iso 中的内容. 在代码中。

在我看来,它的效果要好一些,但我知道这恰好是特定于应用程序的,YMMV。

于 2008-11-04T13:27:41.637 回答
1

如果您使用 C++,您的 A、B 和 C 应用程序不应该继承自一个共同的祖先吗?那将是解决问题的OO方式。

于 2008-11-04T13:12:08.550 回答
0

查看Alexandrescu 的 Modern C++ Design。他介绍了使用模板的基于策略的开发。基本上,这种方法是策略模式的扩展,不同之处在于所有选择都是在编译时做出的。我认为 Alexandrescu 的方法类似于使用 PIMPL 习语,但使用模板实现。

您将使用公共头文件中的预处理标志来选择要编译的实现,并将其类型定义为代码库中其他地方的所有模板实例化中使用的类型。

于 2009-04-30T15:20:48.910 回答
0

To address the specific technical problem of not knowing when a preprocessor define is defined or not, there is a simple but effective trick.

Instead of -

#define APP_A 0
#define APP_B 1
#define APP_C 2

Use -

#define APP_A() 0
#define APP_B() 1
#define APP_C() 2

And in the place that queries for the version use -

#if APPLICATION >= APP_B()
// extra features for APPB and APP_C
#endif

(potentially do something with APPLICATION as well in the same spirit).

Trying to use an undefined preprocessor function would produce a warning or an error by most compilers (whereas an undefined preprocessor define simply evaluates to 0 silently). If the header isn't included, you would immediately notice - especially if you "treat warnings as errors".

于 2009-04-30T15:04:40.570 回答
0

您可能希望了解支持产品线开发并以结构化方式促进显式变体管理的工具。

其中一个工具是来自纯系统的 pure::variants,它能够通过特征模型进行可变性管理,并跟踪在源代码中实现特征的各个位置。

您可以从特征模型中选择特定的特征子集,检查特征之间的约束,并创建产品线的具体变体,即创建一组特定的源代码文件和定义。

于 2008-11-04T13:40:27.307 回答