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 int
s 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
I'm not sure to understand your versioning system. Can't you decouple the objects definitions from the reading?
then
then in one (big) file
You can place the enum in a separate .cfg file, as configuration data, then each of the other source files reads that configuration file.
Then when the configuration file is changed, no re-compile is needed.
All the other files read/parse the configuration file.
This is the classic method of handling such info.
Note: the configuration file would not contain an enum but rather certain formatted data.