One Definition Rule (ODR) 是 C++ 的基本规则,要求在整个程序(所有翻译单元)中,每个对象、函数或类的实现(definition)只有一个。
ODR 被违反的情况包括:
这会导致难以捕捉、不可预测的错误,不正确的链接,或者更糟糕的是,程序的行为会根据其编译方式不同而不同。
为什么会违反:
在大型项目中,常常会在没有版本控制的情况下复制/修改 .h 文件,或者将代码分隔成多个独立编译的模块。如果有人仅在一个地方修改了内联函数,其他源文件可能仍然包含旧版本。
如何避免:
constexpr 或在 C++17 中使用 inline 变量。可以在不同的 .cpp 文件中定义具有相同名称和不同实现的 static 函数 (static void foo()) 吗?没有后果吗?
许多人认为 static 函数在模块之间互不影响。答案是:可以,因为它们各自具有内部链接(仅在其翻译单元内可见)。然而,对于内联函数和模板,这点并不能保证,常常被混淆。
示例:
// file1.cpp static void foo() { std::cout << "A"; } // file2.cpp static void foo() { std::cout << "B"; }
这些模块中的调用将是独立的。
故事
在一个大型项目中,一名开发者仅在一个 .h 文件的克隆中修改了内联函数的主体。在构建后,某些行为变得不可预测:部分模块基于旧算法工作,部分则基于新算法。原因是内联函数的 ODR 被违反。
故事
在迁移到 C++17 时,常量变量在多个头文件中定义,而没有使用关键字 inline。在链接时出现了许多重复符号错误。通过声明
inline const变量来修复。
故事
后来发现,生成的构建系统 .h 文件包含同一模板类的两个实现,这两个实现因在不同构建中存在一个 ifdef 检查而不同。后来,由于关联模块之间的 ABI 不一致,应用程序经常崩溃。