在头强制重新编译中定义的c++枚举的末尾添加值

does adding a value at the end to a c++ enum defined in an header force recompilation?

本文关键字:c++ 定义 枚举 添加 新编译 编译      更新时间:2023-10-16

背景

有一个复杂的系统,其中有几个库和应用程序,几乎每个项目都依赖于一个头文件(例如foo.h),该头文件定义了一个枚举(包含在几个cpp文件中的#)。该设计远非理想(但和往常一样,它是一个遗留系统)。

遗憾的是,foo.h经常更改。

先决条件

假设我们可以保证foo.h将被更新:只在末尾添加元素,而不删除现有值,也不重新定义它们。

问题

这样的修改是否需要重新编译包含标头的所有代码?我知道这是类的一个常见问题(通常删除一个未使用的变量会改变内存布局,并最终导致核心转储)。我怀疑枚举也可能是类似的情况,在正常情况下,我肯定会重新编译所有内容。当前的情况在所有代码库中引入了一种刚性,这对系统的发展方式产生了巨大影响。

我想知道我是否"必须"重新编译。这些信息可用于制定重构策略。

注意

我看过这个问题,但我认为它实际上并没有回答我的问题。

默认答案是:如果您的翻译单元(=预处理的源文件)的内容发生了变化,您必须重新编译它,以使其目标文件与其源格式同步。

头文件#included的更改会导致翻译单元的预处理源发生更改,因此必须重新编译才能完全同步。因此,从这个角度来看,头中的更改需要重新编译。


然而,这是最保守的方法。(如果任何更棘手的方法出现了问题,你应该重新开始)。在实践中,如果您所做的只是将枚举器添加到枚举中,则可能不需要重新编译

如果在头中引入新的枚举器e42,但在文件x.cpp中的任何位置都没有实际使用它,那么从定义了e42x.cpp生成的对象文件很可能与从未定义e42x.cpp生成的目标文件100%相同。因此,从这个角度来看,重新编译是毫无意义的。理想情况下,只有当生成的对象文件与更改前的对象文件不同时,才需要重新编译源文件。

假设您没有在文件中使用新的枚举器,那么几乎唯一可以强制更改对象文件的是:枚举类型的大小可以更改。如果您的最大枚举器值是2147483647,并且添加了一个新的枚举器,则枚举本身的大小很可能会从32位跳到64位。

您可以通过使用C++11显式指定枚举的底层类型来绕过这个限制。请确保为要这样扩展的枚举指定它。

enum EnumerationBeingExtended : int
//                            ^^^^^ this part
{
// ... list of enumerators as before
};

如果您的枚举大小(实际上是整个底层类型)是这样固定的,那么只包含枚举的旧定义的对象文件(不包含e42的对象文件)很可能与包含新定义(包含e42的对象文件。

当然,从技术上讲,你将处于格式错误的程序领域,因为程序将违反一个定义规则(不同的翻译单元对该枚举有不同的定义)。然而,如果您所做的只是使用枚举值,那么在实践中可能是完全安全的。

如果你做一些古怪的事情,比如在枚举类型上使用typeid,我会对这个技巧持谨慎态度。但是,如果您只是"正常"使用枚举器和枚举类型本身,那么您应该很好。

当然,理想情况下,您应该将此枚举隔离到仅包含该枚举的头文件,并记录其预期用途。您可能还必须在构建系统中考虑到由于头的更改而没有重新构建文件的情况。

总之:从形式上讲,你会有一个不规范的程序。但在实践中,你应该是安全的。

如果在更改枚举的内容时要避免尽可能多的重新编译,那么有一种方法可能对您有效。

转发声明。

在foo.h中:

enum class MyEnums : int;

在foo2.h中(实际上,我不知道什么文件名适合调用这个文件,但它只包含以下内容):

enum class MyEnums : int
{
Apple,
Orange,
Banana,
};

您通常不需要在头文件中定义不断变化的枚举。我能看到这种情况发生的只有两次,一次是在头中包含实现细节,另一次是命名默认输入。如果是前者,现在是将这些实现移动到其.cpp文件的好时机(除非它们是模板…)。因此,通常只有.cpp文件会包含foo2以一致地获得定义,而需要它的头文件将获得正向声明的枚举。

这仍然会强制重新编译任何包含foo2的cpp文件,但任何包含foo的头文件都不必重新编译。如果它们不必重新编译,那么包含它们的内容也不必重新编辑。如果它们不必重新编译,那么包括他们在内的东西也不必重新汇编,等等。

在一个大型代码库中,通常构建时间会很慢,因为有人更改了一个在其他标头中产生连锁反应的标头。只有强制重新编译cpp文件才是最简单的方法。

顺便说一句,你是对的。向枚举添加值可能会导致它占用不同的空间。这就是为什么我建议的正向声明需要":int"或任何其他类似的数据类型(char、unsigned short等),因为这保证了它总是占用相同的空间,而这正是将其传递到函数中所需的空间;它所需要的空间知识,而不是内容。