关于cpp的enum枚举类型,我需要弄清楚几个问题。
如果多个源文件定义同名的匿名enum,编译不会报错
// a.cpp
enum {
COLUMN_FILE_NAME = 0,
COLUMN_FILE_CONTENT,
COLUMN_NUMBER
};
// b.cpp
enum {
COLUMN_FILE_NAME = 0,
COLUMN_FILE_CONTENT,
COLUMN_NUMBER
};
这两个 enum 是各自翻译单元内部的独立类型,枚举名是编译期常量,不产生外部符号,不会链接冲突。编译、链接都正常。
如果匿名enum放在头文件里,被多个源文件包含也没问题
// columns.h
#pragma once
enum {
COLUMN_FILE_NAME = 0,
COLUMN_FILE_CONTENT,
COLUMN_NUMBER
};
// xx.cpp
#include "columns.h"
// yy.cpp
#include "columns.h"
对于具名enum情况不同,如果多个源文件定义同名的具名enum,就违反了ODR。
One Definition Rule 既单一定义规则,ODR的单一定义原则有两层:
1.同一个 .cpp(翻译单元)里,同一个变量、函数、类、枚举等,只能有一个定义。
int x = 1;
int x = 2; // 同一 TU 内重复定义,编译错误
2.一个程序通常由多个 .cpp 编译链接而成。但是整个程序内,ODR-used 的实体只能有一个定义,这一层如果出错就是链接器报"重复符号"的原因。
// a.cpp
int a = 1;
// b.cpp
int a = 2; // 链接错误:multiple definition of 'a'
虽然枚举的类型名和枚举常量本身并不产生可链接的符号。因此,如果两个源文件中定义了完全相同的枚举,链接器通常会静默通过,但是这并不意味着程序是正确的。
如果具名枚举放在头文件,被多个源文件包含则没有问题
// columns.h
#pragma once
enum Columns {
COLUMN_FILE_NAME = 0,
COLUMN_FILE_CONTENT,
COLUMN_NUMBER
};
// xx.cpp
#include "columns.h"
// yy.cpp
#include "columns.h"
这是因为ODR 第2层有一个重要例外,以下这些实体允许在多个翻译单元中重复定义
- 类类型(class / struct / union)
- 枚举类型(enum)
- inline 函数
- inline 变量(C++17)
- 类模板、模板实体
- 概念(concept,C++20)
- 某些 constexpr / const 相关实体
但必须满足严格条件(全部满足):
- 每个定义出现在不同的翻译单元中;
- 所有定义逐 token 完全相同(同一串代码文本);
- 名字查找在所有定义中指向相同的实体;
- 语义相同(比如重载解析结果一致)。
满足这些,就不算违反 ODR。这条例外的存在是为了让头文件能工作:你把一个类、一个 inline 函数、一个 enum 写进头文件,多个 .cpp 包含它,每个 TU 都有一份定义,但标准允许。
对于同一个源文件里面包含多个枚举定义,不管是匿名枚举还是具名枚举,都会出错
匿名枚举
具名枚举
enum Color { RED };
enum Fruit { RED }; // 错误:RED 重定义
enum Color { RED };
enum Color { GREEN }; // 错误:Color 重定义
enum class(也叫“作用域枚举”或“强类型枚举”,C++11 引入)和普通 enum 的区别
主要差异
| 特性 | 普通 enum |
enum class |
|---|---|---|
| 枚举名作用域 | 泄露到外层作用域(无作用域) | 封装在枚举类型内,必须用 Enum::Value 访问 |
| 隐式转 int | 允许,可直接参与整数运算 | 禁止,必须显式 static_cast |
| 底层类型 | 编译器决定,不可提前指定 | 默认 int,可显式指定 : uint8_t 等 |
| 前向声明 | 较麻烦,需先指定底层类型 | 天然支持(默认 int) |
为什么 enum class 通常更好
避免命名冲突:普通 enum 的枚举名会“泄露”到外层,两个 enum 里有同名的 Red 就会冲突;enum class 的枚举名只在自身作用域内,Color::Red 和 Apple::Red 可以共存。
enum class Color { RED };
enum class Fruit { RED }; //允许枚举值重名
类型安全:普通 enum 可以隐式转成 int,导致 Red == Apple 这种无意义的比较也能编译通过;enum class 禁止隐式转换,编译器能帮你拦住这类错误。