I need to rewrite some c classes and I would like to be able to switch between the new code and the old code (reliable and fast). What would be good options to do so? In general I need some kind of switch that decides what to do, like:
int foo::bar()
{
if (yesUseNew == true)
{
return 1 1;
}
else
{
return 1 2;
}
}
HOW and where could i set yesUseNew? It should be useable in Debug-Build and in Release and it should be applied directly. So reading some xml-Config would be to late.
The attempt, with two code-versions directly in the methods, is only an example. At this point I am not sure on which abstraction level I will do this. The primary question is actually HOW I can distinguish between the versions (fast).
Thanks!
CodePudding user response:
Well, I see the following alternatives :
- At compile time with #define
e.g:
#define V1 //Comment this line if you want V2. You can also define it on the command line of your compiler
void myfunction() {
#ifdef V1
//V1 Implementation
#else
//V2 Impl
#endif
- At runtime with env variables for example
This means you can switch without recompiling.
if (std::getenv("V1"))
//V1 Code
else
//V2 Code
- Add a configuration option to your app, either in the command line or the config file if it has one. If the dual behaviour will last this is the way to go
CodePudding user response:
You could use macros with #ifdef
clauses to separate code parts:
#include <iostream>
#define NEW
int main()
{
#ifdef NEW
{
std::cout << "NEW" << std::endl;
}
#else
{
std::cout << "OLD" << std::endl;
}
#endif
return 0;
}
Prints NEW
if #define NEW
is specified
CodePudding user response:
For the compile-time option the #define
approach has been mentioned already in other answers, so skipping this one here; but I yet throw in polymorphism as an alternative for the run-time solution already provided:
class Runtime
{
public:
// designed to invoke undefined behaviour
// you might test for existance and throw on negative result instead
static Runtime& instance()
{
// TODO: only thread-safe if writing pointers is atomic
// otherwise you might need to care for
// (unfortunately `std::unique_ptr` cannot be wrapped
// in a std::atomic, so you need to do handle on your own)
return *s_instance;
}
virtual void theFunction() = 0;
// yet others
protected:
Runtime() { } // wouldn't want to construct outside the classes
virtual ~Runtime() { } // std::unique_ptr needs to be friend
// for having this protected
template <typename RT>
void init()
{
// TODO: yet needs to be made thread-safe!
if(s_instance)
{
// appropriate error handling, possibly throw!
}
else
{
}
}
// variables/helper functions needed for both variants
private:
// some of might be private if not to be used from deriving classes
static inline std::unique_ptr<Runtime> s_instance;
};
class OldRuntime : public Runtime
{
public:
static void init()
{
RunTime::init<OldRuntime>();
}
void theFunction() override { /* old behaviour */ }
private:
OldRuntime() { } // templated init needs to be friend!
// variables, helper functions, ... needed for the old approach
};
class NewRuntime : public Runtime
{
public:
static void init()
{
RunTime::init<OldRuntime>();
}
void theFunction() override { /* new behaviour */ }
private:
NewRuntime() { } // templated init needs to be friend!
// variables, helper functions, ... needed for the old approach
};
In above code any thread-safety concerns can be omitted if it is expected to be only ever called before any other threads have been started (or these have terminated already) – this then needs to be clearly documented.
You now still need to decide when to use which variant. Environment variable has been mentioned already – this is only an option if you are fine with any instance already running retaining the behaviour it installed at startup time. If you want to get the behaviour updated then this aproach is not suitable!
A command line parameter in constrast can allow you to define which behaviour to use for every instance individually and you don't need to 'spoil' any environments at all.
CodePudding user response:
It's common practice to add macros that expand to 0
or 1
. You can provide a default value:
#ifndef CONFIG_NEW
# define CONFIG_NEW 1
#endif
Whatever build system you use will provide some way of defining these macros by passing a compiler flag, -DCONFIG_NEW=1
or to generate a config.h
file which defines these flags.
Note that with most build systems, it's possible to have multiple build directories that have different configurations. With CMake for example this is can be done by invoking cmake
multiple times with different build directories and different flags.