Перечисление в заголовке вызывает чрезмерную перекомпиляцию
Джон Лакос ссылается на эту проблему как на коварный источник компиляция-время (рисунок 0-3, в его Введении):
Проблема, с которой я сталкиваюсь, заключается в том, что слишком много файлов скомпилировано, потому что существует физическая зависимость от одного перечисления.
У меня есть заголовок с определением перечисления:
// version.h
enum Version {
v1 = 1,
v2, v3, v4, v5, ... v100
};
и это используется сотнями файлов.
Каждый файл определяет класс объектов, которые нужно читать с диска,
используя функцию read()
. Version
используется для определения способа считывания данных.
Каждый раз, когда вводится новый класс или член класса, новая запись добавляется к перечислению
// 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
}
}
и
// 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);
}
}
Теперь, как вы можете видеть, после изменений ObjectA
мы должны ввести новую запись (скажем v100
) в Version
. Следовательно, все файлы type*.cpp
будут скомпилированы, хотя только read()
of ObjectA
действительно нуждается в записи v100
.
Как я могу инвертировать зависимость от перечисления с минимальными изменениями в клиенте (т.е. type*.cpp
), так что компилируются только необходимые файлы .c?
Вот возможное решение, о котором я думал, но мне нужно лучшее:
Я думал, что могу поместить enum в .cpp файл и выставить int
со значениями соответствующих членов перечисления:
//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
сделать псевдоним для типа Version
как-то
//version.h
typedef const int Version;
и вводить только значения const int, которые необходимы каждый раз:
// 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
}
}
но я думаю, что это выглядит очень плохое решение, которое датируется временем предварительного заголовка
Ответы
Ответ 1
Я не уверен, что понимаю вашу систему управления версиями. Разве вы не можете отделить определения объектов от чтения?
// 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];
}
затем
// typeB.cpp
#include"ObjectB.h"
// define ObjectB
void ObjectB::setPar ( float mm, float vv, color cc)
{
mass = mm;
velocity = vv;
color = cc;
}
затем в одном (большом) файле
// 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)
{
...
}
Ответ 2
Вы можете поместить перечисление в отдельный файл .cfg в качестве данных конфигурации, затем каждый из других исходных файлов считывает этот файл конфигурации.
Затем, когда файл конфигурации изменен, повторная компиляция не требуется.
Все остальные файлы читают/анализируют файл конфигурации.
Это классический метод обработки такой информации.
Примечание: файл конфигурации не будет содержать перечисление, а скорее определенные форматированные данные.