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.
我有三个密切相关的应用程序,它们是从相同的源代码构建的 - 比方说APP_A,APP_B和APP_C。 APP_C是APP_B的超集,APP_B又是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)
我的IDE构建选项然后指定(例如)
#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.
然而,我今天早上在脚上开枪,并且浪费了很多时间,只需从一个文件中省略#include“app_defines.h”。一切都编译好,但应用程序在启动时与AVs崩溃。
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.
我想知道处理这个问题的更好方法是什么。以前,这通常是我认为#define可以使用的少数几次之一(无论如何都是在C ++中),但我仍然很糟糕,编译器没有保护我。
11 个解决方案
#1
12
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].
有一个旧的UNIX技巧,您可以根据argv [0]定制应用程序的行为,即应用程序名称。如果我没记错的话(我看了它已经20年了),rsh和rlogin是同一个命令。您只需根据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.
如果您想坚持使用构建配置,则这是通常使用的模式。您的构建系统/ 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
This pattern enforces that the configuration is command line defined and is valid.
此模式强制配置是命令行定义且有效。
#2
7
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/
你要做的似乎与“产品线”非常相似。 Carnigie Melon大学在这里有一个很好的页面: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.
这基本上是一种构建具有不同功能的一个软件的不同版本的方法。如果您想象像Quicken Home / Pro / Business这样的东西,那么您就走上了正轨。
While that may not be exactly what you attempting, the techniques should be helpful.
虽然这可能不是您尝试的,但这些技术应该会有所帮助。
#3
2
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.
然后控制这些部分中的哪些部分进入构建,其中头文件用于编译*以及哪些.obj文件包含在链接器阶段中。
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.
你可能一开始觉得这有点挣扎。从长远来看,您应该拥有更可靠,可验证的施工和维护流程。您还应该能够进行更好的测试而不必担心所有#if变体。
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.
无论您是使用C ++类还是在C / C ++通用语言级别运行,这都是相同的游戏。
#4
1
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.
如果您使用的是C ++,那么A,B和C应用程序不应该从共同的祖先继承吗?这将是解决问题的OO方式。
#5
1
You may also find some help in this similar one I asked: Writing cross-platform apps in C
您可能也会在我提出的类似问题中找到一些帮助:用C编写跨平台应用程序
#6
1
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.
问题是使用名称未定义的#if指令就好像它被定义为0.这可以通过始终首先执行#ifdef来避免,但这既麻烦又容易出错。
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
并使用app_defines.h来执行命名空间别名
#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.
优点是,当您忘记标题时,您将从编译器i.s.o中获得未知的命名空间错误。默认失败,因为APPLICATION默认为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.
话虽这么说,我一直处于类似的情况,我选择将所有内容重构为许多库,其中绝大多数是共享代码,并让版本控制系统处理不同应用程序i.s.o中的内容。依赖于代码中的定义等。
It works a bit better in my opionon, but I'm aware that happens to be very application specific, YMMV.
它在我的opionon中工作得更好,但我知道这恰好是特定应用程序,YMMV。
#7
1
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 ...
将您的公共代码放在CommonApp类中,并在战略位置调用“AppExtender”接口。例如,AppExtender接口将具有诸如afterStartup,afterConfigurationRead,beforeExit,getWindowTitle等函数...
Then in the main of each application, create the correct extender and pass it to the CommonApp:
然后在每个应用程序的主要部分中,创建正确的扩展程序并将其传递给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();
#8
1
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.
然而,我今天早上在脚上开枪,并且浪费了很多时间,只需从一个文件中省略#include“app_defines.h”。一切都编译好,但应用程序在启动时与AVs崩溃。
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).
这个问题有一个简单的解决方法,打开警告,这样如果没有定义APP_B,那么你的项目就不会编译(或至少产生足够的警告,以便你知道出错了)。
#9
0
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.
您可以从要素模型中选择要素的特定子集,检查要素之间的约束,以及产品系列的具体变体,即创建一组特定的源代码文件和定义。
#10
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).
(也可以用同样的精神对APPLICATION执行某些操作)。
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".
尝试使用未定义的预处理器函数会产生大多数编译器的警告或错误(而未定义的预处理器定义只是静默地评估为0)。如果未包含标题,您会立即注意到 - 特别是如果您“将警告视为错误”。
#11
0
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.
查看Alexandrescu的Modern C ++ Design。他使用模板介绍了基于策略的开发。基本上,这种方法是策略模式的扩展,不同之处在于所有选择都是在编译时完成的。我认为Alexandrescu的方法类似于使用PIMPL习惯,但是使用模板实现。
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.
您可以在公共头文件中使用预处理标志来选择要编译的实现,并将其设置为在代码库中其他位置的所有模板实例中使用的类型。
#1
12
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].
有一个旧的UNIX技巧,您可以根据argv [0]定制应用程序的行为,即应用程序名称。如果我没记错的话(我看了它已经20年了),rsh和rlogin是同一个命令。您只需根据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.
如果您想坚持使用构建配置,则这是通常使用的模式。您的构建系统/ 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
This pattern enforces that the configuration is command line defined and is valid.
此模式强制配置是命令行定义且有效。
#2
7
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/
你要做的似乎与“产品线”非常相似。 Carnigie Melon大学在这里有一个很好的页面: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.
这基本上是一种构建具有不同功能的一个软件的不同版本的方法。如果您想象像Quicken Home / Pro / Business这样的东西,那么您就走上了正轨。
While that may not be exactly what you attempting, the techniques should be helpful.
虽然这可能不是您尝试的,但这些技术应该会有所帮助。
#3
2
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.
然后控制这些部分中的哪些部分进入构建,其中头文件用于编译*以及哪些.obj文件包含在链接器阶段中。
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.
你可能一开始觉得这有点挣扎。从长远来看,您应该拥有更可靠,可验证的施工和维护流程。您还应该能够进行更好的测试而不必担心所有#if变体。
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.
无论您是使用C ++类还是在C / C ++通用语言级别运行,这都是相同的游戏。
#4
1
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.
如果您使用的是C ++,那么A,B和C应用程序不应该从共同的祖先继承吗?这将是解决问题的OO方式。
#5
1
You may also find some help in this similar one I asked: Writing cross-platform apps in C
您可能也会在我提出的类似问题中找到一些帮助:用C编写跨平台应用程序
#6
1
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.
问题是使用名称未定义的#if指令就好像它被定义为0.这可以通过始终首先执行#ifdef来避免,但这既麻烦又容易出错。
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
并使用app_defines.h来执行命名空间别名
#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.
优点是,当您忘记标题时,您将从编译器i.s.o中获得未知的命名空间错误。默认失败,因为APPLICATION默认为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.
话虽这么说,我一直处于类似的情况,我选择将所有内容重构为许多库,其中绝大多数是共享代码,并让版本控制系统处理不同应用程序i.s.o中的内容。依赖于代码中的定义等。
It works a bit better in my opionon, but I'm aware that happens to be very application specific, YMMV.
它在我的opionon中工作得更好,但我知道这恰好是特定应用程序,YMMV。
#7
1
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 ...
将您的公共代码放在CommonApp类中,并在战略位置调用“AppExtender”接口。例如,AppExtender接口将具有诸如afterStartup,afterConfigurationRead,beforeExit,getWindowTitle等函数...
Then in the main of each application, create the correct extender and pass it to the CommonApp:
然后在每个应用程序的主要部分中,创建正确的扩展程序并将其传递给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();
#8
1
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.
然而,我今天早上在脚上开枪,并且浪费了很多时间,只需从一个文件中省略#include“app_defines.h”。一切都编译好,但应用程序在启动时与AVs崩溃。
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).
这个问题有一个简单的解决方法,打开警告,这样如果没有定义APP_B,那么你的项目就不会编译(或至少产生足够的警告,以便你知道出错了)。
#9
0
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.
您可以从要素模型中选择要素的特定子集,检查要素之间的约束,以及产品系列的具体变体,即创建一组特定的源代码文件和定义。
#10
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).
(也可以用同样的精神对APPLICATION执行某些操作)。
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".
尝试使用未定义的预处理器函数会产生大多数编译器的警告或错误(而未定义的预处理器定义只是静默地评估为0)。如果未包含标题,您会立即注意到 - 特别是如果您“将警告视为错误”。
#11
0
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.
查看Alexandrescu的Modern C ++ Design。他使用模板介绍了基于策略的开发。基本上,这种方法是策略模式的扩展,不同之处在于所有选择都是在编译时完成的。我认为Alexandrescu的方法类似于使用PIMPL习惯,但是使用模板实现。
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.
您可以在公共头文件中使用预处理标志来选择要编译的实现,并将其设置为在代码库中其他位置的所有模板实例中使用的类型。