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?
// 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)
{
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With