27

Is it a good practice to use #ifdef during development to switch between different types of behavior? For example, I want to change the behavior of existing code, I have several ideas how to change the behavior and it's necessary to switch between different implementations to test and compare different approaches. Usually changes in code are complex and make influence on different methods in different files.

I usually introduce several identifiers and do something like that

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

void bar()
{
    doSomething3();
#ifndef APPROACH3
    doSomething4();
#endif
    doSomething5();
#ifdef APPROACH2
    bar_approach2();
#endif
}

int main()
{
    foo();
    bar();
    return 0;
}

This allows to switch between different approaches quickly and do everything in only one copy of source code. Is it a good approach for development or is there a better practice?

  • 5
    Possible duplicate of Is it better to use preprocessor directive or if(constant) statement? – gnat Dec 20 '17 at 11:24
  • 2
    Since you are talking about development, I believe you must do whatever you find easy to do to switch and experiment with different implementations. This is more like personal preferences during development, not some best practice to solve a specific problem. – Emerson Cardoso Dec 20 '17 at 11:48
  • 1
    I'd recommend using the strategy pattern or good ol' polymorphism, since this helps keeping a single plug-in point for the switchable behavior. – pmf Dec 20 '17 at 15:08
  • 4
    Bear in mind that some IDEs don't evaluate anything in #ifdef blocks if the block is turned off. We ran into cases where code can easily go stale and not compile if you don't routinely build all paths. – Berin Loritsch Dec 20 '17 at 19:19
  • Have a look at this answer I gave to another question. It lays out some ways to make lots of #ifdefs less cumbersome. – user1118321 Dec 20 '17 at 19:33
9

I would prefer using version control branches for this use case. That allows you to diff between the implementations, maintain a separate history for each, and when you have made your decision and need to remove one of the versions, you just discard that branch instead of going through an error-prone edit.

  • git is especially adept at this sort of thing. Maybe not so much the case with svn, hg or others, but it can still be done. – twalberg Dec 20 '17 at 19:30
  • That was my initial thought too. "Want to mess around with something different?" git branch! – Wes Toleman Dec 21 '17 at 1:21
42

When you're holding a hammer, everything looks like a nail. It's tempting once you know how #ifdef works to use it as a sort of means to obtaining custom behavior in your program. I know because I made the same mistake.

I inherited a legacy program written in MFC C++ which already used #ifdef to define platform-specific values. This meant I could compile my program to be used on a 32-bit platform or a 64-bit platform simply by defining (or in some cases not defining) specific macro values.

The problem then arose that I needed to write custom behavior for a client. I could have created a branch and made a separate code base for the client, but that would have made a maintenance hell. I could have also defined configuration values to be read by the program on startup and used these values to determine behavior, but I would then have to create custom setups to add the proper configuration values to the configuration files for each client.

I was tempted, and I gave in. I wrote #ifdef sections in my code to differentiate the various behavior. Make no mistake, it was nothing over the top at first. Very minor behavior changes were made which allowed me to redistribute versions of the program to our clients and I need not have more than one version of the code base.

Over time this became maintenance hell anyway because program no longer behaved consistently across the board. If I wanted to test a version of the program, I had to necessarily know who the client was. The code, though I tried to reduce it to one or two header files, was very cluttered and the quick fix approach that #ifdef provided meant such solutions spread throughout the program like a malignant cancer.

I've since learned my lesson, and you should too. Use it if you absolutely must, and use it strictly for platform changes. The best way to approach behavior differences between programs (and therefore clients) is to change only the configuration loaded on startup. The program remains consistent and it both becomes easier to read as well as to debug.

  • what about debug versions, like "if debug, define variable x..." this seems like it could be usefull for things like logging, but then it could also totally change how your program works when debug is enabled and when it isn't. – opa Dec 20 '17 at 14:34
  • 8
    @snb I thought about that. I still prefer being able to change a configuration file and making it log with more detail. Otherwise something goes wrong with the program in production, and you have no way of debugging it without completely replacing the executable. Even in ideal situations, this is less than desirable. ;) – Neil Dec 20 '17 at 14:37
  • Oh yeah, it would be way more ideal to not have to recompile to debug, I didn't think about that! – opa Dec 20 '17 at 14:39
  • 9
    For an extreme example of what you're describing, look at the 2nd paragraph under the "The Problem of Maintainability" subheading in this article about why MS reached the point where they had to rewrite most of their C runtime from scratch a few years ago. blogs.msdn.microsoft.com/vcblog/2014/06/10/… – Dan Neely Dec 20 '17 at 16:34
  • 2
    @snb Most logging libraries suppose a logging level mechanism. If you want certain info logged during debugging, you log it with a low logging level ("Debug" or "Verbose" usually). Then the application has a configuration parameter that tells it what level to log. So the answer is still configuration for this problem. This also has the enormous benefit of being able to turn on this low logging level in a client environment. – jpmc26 Dec 20 '17 at 23:35
21

Temporarily there is nothing wrong with what you're doing (say, before check-in): it's a great way to test different combinations of techniques, or to ignore a section of code (though that speaks of problems in and of itself).

But a word of warning: do not keep #ifdef branches there is little more frustrating than wasting my time reading the same thing implemented four different ways, only to figure out which one I should be reading.

Reading over an #ifdef takes effort as you have to actually remember to skip over it! Don't make it any harder than it absolutely has to be.

Use #ifdefs as sparingly as you can. There are generally ways that you can do this within your development environment for permanent differences, such as Debug / Release builds, or for different architectures.

I have written library features that were dependent on included library versions, which required #ifdef splits. So at times it may be the only way, or the easiest, but even then you should be upset about keeping them.

1

Using #ifdefs like that makes code very hard to read.

So, no, don't use #ifdefs like that.

There may be tons of arguments why not to use ifdefs, for me this one is enough.

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

Can do a lot of things it can do:

void foo()
{
    doSomething1();
    doSomething2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
}

void foo()
{
    doSomething1();
    doSomething2();
    foo_approach2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
    foo_approach2();
}

All depending on what approaches are defined or not. What it does is absolutely not clear on first look.

protected by gnat Dec 21 '17 at 14:26

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.