【问题标题】:enum in header causes excessive recompilations标头中的枚举导致过度重新编译
【发布时间】: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),并且每个源文件可以只包含它所需的最低版本的标头。这样只有源需要一个新的版本取决于新的或更改的标头。当然,这可能会为预处理器做很多工作..

标签: c++ c


【解决方案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)
{
    ...
}

【讨论】:

  • 如果我这样做,那么我会将这个文件与 all “ObjectXXX.h”文件结合起来。我将减少更改对系统上“version.h”的影响,但“readObject.cpp”将有数千个重新编译的理由。但是,这种方法有的好处。
  • @Grim Fandango:是的,但至少它是 1 个文件。所有未修改的 ObjectX.cpp 都不会重新编译。
【解决方案2】:

您可以将枚举作为配置数据放在单独的 .cfg 文件中,然后其他每个源文件都会读取该配置文件。

那么当配置文件改变时,不需要重新编译。

所有其他文件读取/解析配置文件。

这是处理此类信息的经典方法。

注意:配置文件不会包含枚举,而是某些格式化数据。

【讨论】:

  • 通过“每个其他源文件读取该配置文件”,我想你的意思是每个源文件中的函数读取配置文件在运行时,或者什么达到一般效果。是吗?
  • 对,没错,每个源文件都打开配置文件读取,读取内容,解析内容,使用内容
  • 你能提供一个例子吗?我不确定我是否跟随。
猜你喜欢
  • 2019-07-21
  • 1970-01-01
  • 1970-01-01
  • 2018-12-12
  • 2011-06-23
  • 2012-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多