Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enum in header causes excessive recompilations

Tags:

c++

c

John Lakos refers to this problem as an insidious source of compile-time coupling (Figure 0-3, in his Introduction):

The problem I am facing is that too many files get compiled because there is a physical dependency on a single enum.

I have a header with the enum definition:

// version.h
enum Version {
    v1 = 1,
    v2, v3, v4, v5, ... v100
};

and this is used by hundreds of files. Each file defines a class of objects, which have to be read from the disk, using the read() function. Version is used to determine the way data is to be read.

Every time a new class or class member is introduced, a new entry is appended to the enum

// typeA.cpp
#include "version.h"

void read (FILE *f, ObjectA *p, Version v) 
{
    read_float(f, &p->x);
    read_float(f, &p->y);
    if (v >= v100) { 
        read_float(f, &p->z);  // after v100 ObjectA becomes a 3D
    }
}

and

// typeB.cpp
#include "version.h"

void read (FILE *f, ObjectB *p, Version v)
{
    read_float (f, &p->mass);
    if (v >= v30) {
        read_float (f, &p->velocity);
    }
    if (v >= v50) {
        read_color (f, &p->color);
    }
}

Now, as you can see, once ObjectA changes, we have to introduce a new entry (say v100) to the Version. Consequently all type*.cpp files will be compiled, even though only read() of ObjectA really needs the v100 entry.

How can I invert the dependency on the enum, with minimal changes to the client (i.e. type*.cpp) code, so that only the necessary .c files compile ?

Here is a possible solution, that I thought of, but I need a better one:

I was thinking that I could put the enum in a .cpp file, and expose ints with the values of the respective enum members:

//version.cpp
enum eVersion {
    ev1 = 1,
    ev2, ev3, ev4, ev5, ... ev100
};

const int v1 = ev1;
const int v2 = ev2;
....
const int v100 = ev100;   // introduce a new global int for every new entry in the enum

make an alias for the Version type somehow

//version.h
typedef const int Version;

and introduce only the const int values that are needed each time:

// typeA.cpp
#include "version.h"

extern Version v100;    ///// *** will be resolved at link time

void read (FILE *f, ObjectA *p, Version v) 
{
    read_float(f, &p->x);
    read_float(f, &p->y);
    if (v >= v100) { 
        read_float(f, &p->z);  // after v100 ObjectA becomes a 3D
    }
}

but I think this looks like very poor solution, which dates back to pre-header times

like image 894
Grim Fandango Avatar asked Nov 10 '22 01:11

Grim Fandango


1 Answers

I'm not sure to understand your versioning system. Can't you decouple the objects definitions from the reading?

// ObjectA.cpp

#include"ObjectA.h"  

// define ObjectA

void ObjectA::setPar ( float xx, float yy, float zz) 
{
    x = v[0];
    y = v[1];
    z = v[2]; 
}

then

// typeB.cpp

#include"ObjectB.h"  

// define ObjectB

void ObjectB::setPar ( float mm, float vv, color cc) 
{
    mass = mm;
    velocity = vv;
    color = cc; 
}

then in one (big) file

// readObject.cpp

#include"version.h"
#include"ObjectA.h"
#include"ObjectB.h"

void read (FILE *f, ObjectA *p, Version v) 
{
    float x,y,z;
    read_float(f, x);
    read_float(f, y);
    if (v >= v100) { 
        read_float(f, z);  // after v100 ObjectA becomes a 3D
    } else z=0.0;          // whatever
    p->setPar(x,y,z);
}

void read (FILE *f, ObjectB *p, Version v)
{
    ...
}
like image 173
Bob__ Avatar answered Nov 14 '22 22:11

Bob__