Перечисление в заголовке вызывает чрезмерную перекомпиляцию

Джон Лакос ссылается на эту проблему как на коварный источник компиляция-время (рисунок 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 в качестве данных конфигурации, затем каждый из других исходных файлов считывает этот файл конфигурации.

Затем, когда файл конфигурации изменен, повторная компиляция не требуется.

Все остальные файлы читают/анализируют файл конфигурации.

Это классический метод обработки такой информации.

Примечание: файл конфигурации не будет содержать перечисление, а скорее определенные форматированные данные.