What is the best way to create variations of the same C / C ++ application

I have three closely related applications that are built from the same source code - 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 have used the definition of a preprocessor to indicate which application to create, which works as follows.

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

IDE build options then indicate (for example)

 #define APPLICATION APP_B 

... and in the 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 spent a lot of time just dropping the line #include "app_defines.h" from one file. Everything compiled in order, but the application crashes with AVs when loading.

I would like to know what is the best way to handle this. This used to be one of the few times when I could consider using #define (anyway, in C ++), but I'm still not good at it, and the compiler did not protect me.

+6
c ++ c-preprocessor configuration-management software-product-lines
source share
11 answers

You do not always need to force inherit relationships in applications that use a common code base. In fact.

There is an old UNIX trick in which you configure the behavior of your application based on argv [0], that is, the name of the application. If I remember correctly (and it has been 20 years since I looked at it), rsh and rlogin were / were the same team. You simply run the runtime configuration based on the value of argv [0].

If you want to stick with the assembly configuration, this is the template that is commonly used. Your build system / makefile defines the character in the command, for example APP_CONFIG, for a non-zero value, then you have a common include file with 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 template establishes that the configuration is specified and valid on the command line.

+12
source share

What you are trying to do seems to be similar to Product Lines. Carnigie Melon University has a great template page: http://www.sei.cmu.edu/productlines/

This is basically a way to create different versions of the same software with different capabilities. If you fancy something like Quicken Home / Pro / Business, then you are on your way.

Although this may not be exactly what you are trying, methods should be useful.

+6
source share

It seems to me that you can look at the modularity of your code for separately compiled elements by building options from a set of common modules and a specific top-level (main) module.

Next, determine which of these parts are part of the assembly with which the header files are used in top-level compilation and which .obj files you include in the linker phase.

It may seem a little difficult at first. Ultimately, you must have a more reliable and proven construction and maintenance process. You should also be able to do better testing without worrying about all the #if options.

I hope that your application is not yet too large, and to unravel the modularity of its functions will not have to deal with a large ball of dirt.

At some point, you may need to check the runtime to make sure that the assembly used consistent components to configure the application that you intended, but this can be clarified later. You can also do some compilation-time consistency checking, but you will get most of this with header files and signatures of entry points to sub modules that come in a particular combination.

This is the same game, whether you use C ++ classes or work at the C / C ++ common language level.

+2
source share

If you use C ++, should your applications A, B and C not inherit from a common ancestor? This will be an OO way to solve the problem.

+1
source share

You can also find help with this, I asked: Writing cross-platform applications in C

+1
source share

The problem is that using the #if directive with a name that undefined acts as if it is defined as 0. This can be avoided by always making #ifdef first, but it is cumbersome and error prone.

A slightly better way is to use an alias and namespace.

eg.

 namespace AppA { // application A specific } namespace AppB { // application B specific } 

And use app_defines.h to create namespace aliases

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

Or, if more complex combinations, namespace placement

 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 will get unknown namespace errors from your iso compiler due to an unsuccessful failure, because by default APPLICATION is 0.

It is said that I was in a similar situation, I decided to process everything into many libraries, of which the vast majority was common code, and let the version control system process what happens where in another application iso is based on definitions, etc. in code.

It works a little better in my opionon, but I know that it is very specific to the YMMV application.

+1
source share

Do something like this:

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

Put your common code in the CommonApp class and place the calls in the AppExtender interface in strategic places. For example, the AppExtender interface will have features such as afterStartup, afterConfigurationRead, beforeExit, getWindowTitle ...

Then, in the main window of each application, create the correct extender and pass it to 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(); 
+1
source share

However, I shot myself in the foot this morning and spent a lot of time just dropping the line #include "app_defines.h" from one file. Everything compiled in order, but the application crashes with AVs when loading.

There is a simple fix for this problem, include warnings so that if APP_B is not defined, your project will not compile (or at least generate enough warnings to let you know that something is wrong).

+1
source share

You might want to take a look at tools that support product line development and structure ways to manage an explicit option.

One of these tools is pure :: options from pure-systems , which is capable of managing variability using function models and tracking the various places where the function is implemented in the source code.

You can select a specific subset of the function from the function model, check the restrictions between the functions, and a specific version of your product line is created, that is, a specific set of files and source code definitions.

0
source share

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

Instead

 #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 that place where requests for the version are used -

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

(perhaps doing something with APPLICATION is also in the same vein).

Trying to use preprocessor> would create a warning or error by most compilers (whereas the undefined define preprocessor simply calculates 0 without sound). If the header is not included, you will immediately notice - especially if you "treat warnings as errors."

0
source share

Check out the Alexandrescu Modern C ++ Design . He presents policy-based development using templates. In principle, this approach is an extension of the strategy template with the difference that all selections are made at compile time. I think the Alexandrescu approach is similar to using the PIMPL idiom, but is implemented using templates.

You would use the preprocessing flags in the general header file to select which implementation you want to compile, and typedef for the type used in all template instances elsewhere in your code base.

0
source share

All Articles