构建相同 C/C 的变体的最佳方法是什么? 应用

发布于 2024-07-08 00:44:40 字数 697 浏览 8 评论 0原文

我有三个密切相关的应用程序,它们是从相同的源代码构建的 - 比如说 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.txt" 行来浪费了很多时间。 h”来自一个文件。 一切都编译得很好,但应用程序在启动时因 AV 崩溃了。

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

I have three closely related applications that are build from the same source code - let's say APP_A, APP_B, and APP_C. APP_C is a superset of APP_B which in turn is a superset of APP_A.

So far I've been using a preprocessor define to specify the application being built, which has worked like this.

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

My IDE build options then specify (for example)

#define APPLICATION APP_B

... and in source code, I will have things like

#include "app_defines.h"

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

However, I shot myself in the foot this morning and wasted far to much time by simply omitting the line to #include "app_defines.h" from one file. Everything compiled fine, but the application crashed with AVs at startup.

I'd like to know what a better way of handling this would be. Previously, This would normally one of the few times when I'd consider #define could be used (in C++, anyway), but I still goofed up badly and the compiler didn't protect me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

浅忆流年 2024-07-15 00:44:40

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

有一个古老的 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

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

You don't always have to force inheritance relationships in applications that share a common code base. Really.

There's an old UNIX trick where you tailor the behavior of you application based on argv[0], ie, the application name. If I recall correctly (and it's been 20 years since I looked at it), rsh and rlogin are/were the same command. You simply do runtime configuration based on the value of argv[0].

If you want to stick with build configuration, this is the pattern that is typically used. Your build system/makefile defines a symbol on the command like, APP_CONFIG to be a non-zero value then you have a common include file with the configuration nuts and bolts.

#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

This pattern enforces that the configuration is command line defined and is valid.

俏︾媚 2024-07-15 00:44:40

您想要做的事情似乎与“产品线”非常相似。 卡尼基梅隆大学有一个关于该模式的优秀页面:http://www.sei.cmu.edu/ Productlines/

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

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

What you are trying to do seems very similar to "Product lines". Carnigie Melon University has an excellent page on the pattern here: http://www.sei.cmu.edu/productlines/

This is basically a way to build different versions of one piece of software with different capabilities. If you imagine something like Quicken Home/Pro/Business then you are on track.

While that may not be exactly what you attempting, the techniques should be helpful.

忆悲凉 2024-07-15 00:44:40

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

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

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

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

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

无论您使用 C++ 类还是在 C/C++ 通用语言级别进行操作,这都是同一个游戏。

It sounds to me that you might look at modularizing your code into separately-compiled elements, building the variants from a selection of common modules and a variant-specific top-level (main) module.

Then control which ones of these parts go into a build by which header files are used in compiling the top level and which .obj files you include into the linker phase.

You might find this a bit of a struggle at first. In the long run you should have a more reliable and verifiable construction and maintenance process. You should also be able to do better testing without worrying about all the #if variations.

I'm hoping that your application is not terribly large just yet and unraveling a modularization of its functions won't have to deal with a big ball of mud.

At some point you might need run-time checks to verify that the build used consistent components for the application configuration you intended, but that can be figured out later. You can also achieve some compile-time consistency checking, but you'll get most of that with header files and signatures of entry points into the subordinate modules that go into a particular combination.

This is the same game whether you are using C++ classes or operating pretty much at the C/C++ common-language level.

梦幻之岛 2024-07-15 00:44:40

如果您使用 C++,您的 A、B 和 C 应用程序不应该继承共同的祖先吗? 这就是解决问题的面向对象方法。

If you're using C++, shouldn't your A, B, and C applications inherit from a common ancestor? That would be the OO way to solve the problem.

今天小雨转甜 2024-07-15 00:44:40

问题在于,使用带有未定义名称的 #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。

The problem is that using a #if directive with a name that's undefined acts as if it's defined as 0. This could be avoided by always doing an #ifdef first, but that's both cumbersome and error prone.

A slightly better way is to use namespace and namespace aliasing.

E.g.

namespace AppA {
     // application A specific
}

namespace AppB {
    // application B specific
}

And use you app_defines.h to do namespace aliasing

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

Or, if more complex combinations, namespace nesting

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

Or any combination of the above.

The advantage is that when you forget the header you'll get unknown namespace errors from your compiler i.s.o. of silently failing because APPLICATION is defaulted to 0.

That being said, I've been in a similar situation, I chose to refactor everything into many libraries, of which the vast majority was shared code, and let the version control system handle what goes where in the different application i.s.o. relying on defines etc. in the code.

It works a bit better in my opionon, but I'm aware that happens to be very application specific, YMMV.

栖竹 2024-07-15 00:44:40

然而,今天早上我搬起石头砸了自己的脚,仅仅从一个文件中省略了#include“app_defines.h”这一行就浪费了很多时间。 一切都编译得很好,但应用程序在启动时因 AV 崩溃了。

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

However, I shot myself in the foot this morning and wasted far to much time by simply omitting the line to #include "app_defines.h" from one file. Everything compiled fine, but the application crashed with AVs at startup.

There is a simple fix to this problem, turn on the warnings so that if APP_B isn't defined then your project doesn't compile (or at least produces enough warnings so that you know something is wrong).

半枫 2024-07-15 00:44:40

执行如下操作:

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();

Do something like this:

CommonApp   ├─────   AppExtender                       ├─ = containment
                      ▲    ▲    ▲
                      │    │    │                       ▲ = ineritance
                    AppA  AppB  AppC                    │

Put your common code in the class CommonApp and put calls to the interface 'AppExtender' at strategic places. For example the AppExtender interface will have functions like afterStartup, afterConfigurationRead, beforeExit, getWindowTitle ...

Then in the main of each application, create the correct extender and pass it to the 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();
梦里泪两行 2024-07-15 00:44:40

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

这些工具之一是来自 pure-systems 的 pure::variants,它能够通过功能进行可变性管理模型并跟踪源代码中实现功能的各个位置。

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

You might want to have a look at tools that support the development of product lines and foster explicit variant management in a structured way.

One of these tools is pure::variants from pure-systems which is capable of variability management through feature models and of keeping track of the various places a feature is implemented in source code.

You can select a specific subset of feature from the feature model, constraints between features are being checked, and the concrete variant of your product line, that is, a specific set of source code files and defines is created.

烟花肆意 2024-07-15 00:44:40

为了解决不知道预处理器定义何时定义的具体技术问题,有一个简单但有效的技巧。

而不是 -

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

使用 -

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

并且在查询版本的地方使用 -

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

(也可能本着同样的精神对 APPLICATION 做一些事情)。

大多数编译器尝试使用未定义的预处理器函数都会产生警告或错误(而未定义的预处理器define只是默默地计算为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".

金兰素衣 2024-07-15 00:44:40

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

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

Check out Alexandrescu's Modern C++ Design. He presents the policy based development using templates. Basically, this approach is an extension of the strategy pattern with the difference being that all choices are made at compile time. I think of Alexandrescu's approach as being similar to using the PIMPL idiom, but implementing with templates.

You would use the pre-processing flags in a common header file to choose which implementation that you wanted to compile, and typedef that to a type used in all the template instantiations elsewhere in your code-base.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文