Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid long compilation time for #define in common header

I was wondering if there is an elegant way to solve this problem. Suppose there's a common header eg

// common.h
#ifndef COMMON_H
#define COMMON_H

#define ENABLE_SOMETHING
//#define ENABLE_SOMETHING_ELSE
#define ENABLE_WHATEVER
// many others

#endif

Now this file is included by, let's say 100 other header files and the various #define are used to enable or disable some parts of code which are confined to just 1-2 files.

Everytime a single #define is changed the whole project seems to be rebuilt (I'm working on Xcode 5.1), which makes sense as it must be literally replaced all around the code and the compiler can't know a priori where it's used.

I'm trying to find a better way to manage this, to avoid long compilation times, as these defines are indeed changed many times. Splitting each define in their corresponding file/files could be a solution but I'd like the practical way to have everything packed together.

So I was wondering if there is a pattern which is usually used to solve this problem, I was thinking about having

// common.h
class Enables
{
  static const bool feature;
};

// common..cpp
bool Enables::feature = false;

Will this be semantically equivalent when compiling optimized binary? (eg. code inside false enables will totally disappear).

like image 450
Jack Avatar asked Jun 12 '14 13:06

Jack


1 Answers

You have two distinct problems here:

Splitting each define in their corresponding file/files could be a solution but I'd like the practical way to have everything packed together.

This is your first problem. If I undestand correctly, if you have more than one functional area, you are not interested in having to include a header for each of them (but a single header for everything).

Apply these steps:

  • do split the code by functionality, into different headers; Each header should contain (at most) what was enabled by a single #define FEATURESET (and be completely agnostic to the existence of the FEATURESET macro).

  • ensure each header is only compiled once (add #pragma once at the beginning of each feature header file)

  • add a convenience header file that performs #if or #ifdef based on your defined features, and includes the feature files as required:

    // parsers.h
    // this shouldn't be here: #pragma once
    
    #ifdef PARSEQUUX_SAFE
    #include <QuuxSafe.h>
    #elif defined PARSEQUUX_FAST
    #include <QuuxFast.h>
    #else
    #include <QuuxSafe.h>
    #endif
    
    // eventually configure static/global class factory here
    // see explanation below for mentions of class factory
    

Client code:

#include <parsers.h> // use default Quux parser

#define PARSEQUUX_SAFE
#include <parsers.h> // use safe (but slower) Quux parser

So I was wondering if there is a pattern which is usually used to solve this problem

This is your second problem.

The canonical way to enable functionality by feature in C++, is to define feature API, in terms of base classes, class factories and programming to a generic interface.

// common.h
#pragma once
#include <Quux.h> // base Quux class

struct QuuxFactory
{
    enum QuuxType { Simple, Feathered };
    static std::unique_ptr<Quux> CreateQuux(int arg);

    static QuuxType type;
};

// common.cpp:

#include <common.h>
#include <SimpleQuux.h> // SimpleQuux: public Quux
#include <FeatheredQuux.h> // FeatheredQuux: public Quux

std::unique_ptr<Quux> QuuxFactory::CreateQuux(int arg)
{
    switch(type) {
    case Simple:
        return std::unique_ptr<Quux>{new SimpleQuux{arg}};
    case Feathered:
        return std::unique_ptr<Quux>{new FeatheredQuux{arg}};
    };
    // TODO: handle errors
}

Client code:

// configure behavior:
QuuxFactory::type = QuuxFactory::FeatheredQuux;

// ...

auto quux = QuuxFactory::CreateQuux(10); // creates a FeatheredQuux in this case

This has the following advantages:

  • it is straightforward and uses no macros

  • it is reusable

  • it provides an adequate level of abstraction

  • it uses no macros (as in "at all")

  • the actual implementations of the hypothetical Quux functionality are only included in one file (as an implementation detail, compiled only once). You can include common.h wherever you want and it will not include SimpleQuux.h and FeatheredQuux.h at all.

As a generic guideline, you should write your code, such that it requires no macros to run. If you do, you will find that any macros you want to add over it, are trivial to add. If instead you rely on macros from the start to define your API, the code will be unusable (or close to unusable) without them.

like image 144
utnapistim Avatar answered Nov 15 '22 04:11

utnapistim