【发布时间】:2015-11-11 17:01:41
【问题描述】:
John Lakos 将此问题称为 编译时耦合(图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 更改,我们必须在Version 中引入一个新条目(例如v100)。因此,所有type*.cpp 文件都将被编译,即使只有ObjectA 中的read() 真正需要v100 条目。
如何通过对客户端(即type*.cpp)代码的最小更改来反转对枚举的依赖关系,以便仅编译必要的 .c 文件?
这是我想到的一个可能的解决方案,但我需要一个更好的解决方案:
我在想我可以把枚举放在一个 .cpp 文件中,并用各个枚举成员的值公开ints:
//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
}
}
但我认为这看起来是一个非常糟糕的解决方案,可以追溯到预标题时代
【问题讨论】:
-
你能不能只使用一个整数而不是枚举,并使用例如。
if (v >= 30) {而不是if (v >= v30) {? -
无论如何,对
version.h没有真正的依赖,除非编辑重新排序枚举,这似乎超出了预期的行为(因此导致make clean)。 -
@Dmitri 标签(如 v30)更复杂,不容易转换为数字。为了简单起见,我使用了 v1-v100
-
好吧,您的其他每个文件都会重新编译,因为它
#includes 是您修改过的文件,而不是因为它们本身使用枚举。但是,如果您不能摆脱#include或停止修改该标头(或该标头的依赖项),问题将持续存在。您可以将read函数拆分为不同的源文件,以便重新编译更少的代码,我猜。 -
使用宏而不是枚举标签和多个标题(每个版本一个,例如 version_v1.h、version_v2.h 等)。每个标头都可以#include 前一个并根据前一个定义其宏(例如
#include "version_v29.h"然后#define v30 (v29+1),并且每个源文件可以只包含它所需的最低版本的标头。这样只有源需要一个新的版本取决于新的或更改的标头。当然,这可能会为预处理器做很多工作..