定义和 ODR(单一定义规则)
定义是指完全定义声明引入的实体的声明。每个声明都是一个定义,除了以下情况
- 没有函数体的函数声明
int f(int); // declares, but doesn't define f
extern const int a; // declares, but doesn't define a extern const int b = 1; // defines b
- 在类定义内部声明 非内联(自 C++17 起) 的 静态数据成员
struct S { int n; // defines S::n static int i; // declares, but doesn't define S::i inline static int x; // defines S::x }; // defines S int S::i; // defines S::i
struct S { static constexpr int x = 42; // implicitly inline, defines S::x }; constexpr int S::x; // declares S::x, not a redefinition |
(自 C++17 起) |
- 类名的声明(通过 前向声明 或在另一个声明中使用 elaborated 类型说明符)
struct S; // declares, but doesn't define S class Y f(class T p); // declares, but doesn't define Y and T (and also f and p)
enum Color : int; // declares, but doesn't define Color |
(自 C++11 起) |
- 模板参数 的声明
template<typename T> // declares, but doesn't define T
- 函数声明(非定义)中的参数声明
int f(int x); // declares, but doesn't define f and x int f(int x) // defines f and x { return x + a; }
- typedef 声明
typedef S S2; // declares, but doesn't define S2 (S may be incomplete)
using S2 = S; // declares, but doesn't define S2 (S may be incomplete) |
(自 C++11 起) |
using N::d; // declares, but doesn't define d
|
(自 C++17 起) |
|
(自 C++11 起) |
extern template f<int, char>; // declares, but doesn't define f<int, char> |
(自 C++11 起) |
- 声明不是定义的 显式特化
template<> struct A<int>; // declares, but doesn't define A<int>
asm 声明 不定义任何实体,但它被归类为定义。
在必要时,编译器可以隐式定义 默认构造函数、复制构造函数、移动构造函数、复制赋值运算符、移动赋值运算符 和 析构函数。
如果任何对象的定义导致 不完整类型 或 抽象类类型 的对象,则程序是格式错误的。
内容 |
[编辑] 单一定义规则
在任何一个翻译单元中,只允许对任何变量、函数、类类型、枚举类型、概念(自 C++20 起) 或模板进行一次定义(其中一些可能有多个声明,但只允许一个定义)。
在整个程序(包括任何标准和用户定义的库)中,需要出现对每个被 odr-used(见下文)的非 内联 函数或变量的一个且只有一个定义。编译器不需要诊断此违规行为,但违反此规则的程序的行为是未定义的。
对于内联函数 或内联变量(自 C++17 起),在每个 odr-used 它的翻译单元中都需要一个定义。
对于类,在任何需要类为 完整 的地方都需要一个定义。
程序中可以有多个以下各项的定义:类类型、枚举类型、内联函数、内联变量(自 C++17 起)、模板化实体(模板或模板的成员,但不是完整的 模板特化),只要以下所有条件都为真
- 每个定义都出现在不同的翻译单元中
|
(自 C++20 起) |
- 每个定义都由相同的标记序列组成(通常出现在同一个头文件中)
- 从每个定义内部进行名称查找都会找到相同的实体(在重载解析之后),除了
- 具有内部或无链接的常量可以引用不同的对象,只要它们不被 odr-used 并且在每个定义中具有相同的值
|
(自 C++11 起) |
- 重载运算符,包括转换、分配和释放函数,在每个定义中都引用相同的函数(除非引用定义中定义的函数)
- 相应的实体在每个定义中都具有相同的语言链接(例如,包含文件不在 extern "C" 块内)
- 如果
const
对象在任何定义中被 常量初始化,则它在每个定义中都被常量初始化 - 以上规则适用于每个定义中使用的每个默认参数
- 如果定义是针对具有隐式声明的构造函数的类,则每个 odr-used 它的翻译单元都必须为基类和成员调用相同的构造函数
|
(自 C++20 起) |
- 如果定义是针对模板的,则所有这些要求都适用于定义点的名称和实例化点的依赖名称
如果满足所有这些要求,则程序的行为就像整个程序中只有一个定义一样。否则,程序格式错误,不需要诊断。
注意:在 C 语言中,类型没有程序范围的 ODR,甚至在不同翻译单元中对同一变量的 extern 声明也可能具有不同的类型,只要它们是兼容的。在 C++ 中,用于声明同一类型的源代码标记必须与上述描述相同:如果一个 .cpp 文件定义了 struct S { int x; };,而另一个 .cpp 文件定义了 struct S { int y; };,则将它们链接在一起的程序的行为是未定义的。这通常使用 未命名命名空间 来解决。
[编辑] ODR 使用
非正式地说:
- 如果读取对象的值(除非它是编译时常量)或写入对象的值、获取对象的地址或将引用绑定到对象,则该对象被 ODR 使用。
- 如果使用了引用并且在编译时不知道其引用对象,则该引用被 ODR 使用。
- 如果对函数进行了函数调用或获取了函数的地址,则该函数被 ODR 使用。
如果对象、引用或函数被 ODR 使用,则其定义必须存在于程序中的某个位置;违反此规则通常会导致链接时错误。
struct S { static const int x = 0; // static data member // a definition outside of class is required if it is odr-used }; const int& f(const int& r); int n = b ? (1, S::x) // S::x is not odr-used here : f(S::x); // S::x is odr-used here: a definition is required
正式地说:
- 对 x 应用左值到右值转换会产生一个不调用非平凡函数的常量表达式
- x 不是对象(即 x 是引用),或者,如果 x 是对象,它是较大表达式 e 的潜在结果之一,其中该较大表达式要么是 丢弃值表达式,要么对其应用了左值到右值转换
struct S { static const int x = 1; }; // applying lvalue-to-rvalue conversion // to S::x yields a constant expression int f() { S::x; // discarded-value expression does not odr-use S::x return S::x; // expression where lvalue-to-rvalue conversion // applies does not odr-use S::x }
3) 如果 结构化绑定 作为可能被求值的表达式出现,则它被 ODR 使用。 |
(自 C++17 起) |
表达式 E 的一组潜在结果是出现在 E 中的 id 表达式(可能为空)的集合,组合如下
- 如果 E 是 id 表达式,则表达式 E 是其唯一的潜在结果。
- 如果 E 是下标表达式 (E1[E2]),其中一个操作数是数组,则该操作数的潜在结果包含在集合中。
- 如果 E 是命名非静态数据成员的类成员访问表达式,形式为 E1.E2 或 E1.template E2,则 E1 的潜在结果包含在集合中。
- 如果 E 是命名静态数据成员的类成员访问表达式,则指定数据成员的 id 表达式包含在集合中。
- 如果 E 是指向成员的访问表达式,形式为 E1.*E2 或 E1.*template E2,其第二个操作数是常量表达式,则 E1 的潜在结果包含在集合中。
- 如果 E 是括号中的表达式 ((E1)),则 E1 的潜在结果包含在集合中。
- 如果 E 是左值条件表达式 (E1 ? E2 : E3,其中 E2 和 E3 是左值),则 E2 和 E3 的潜在结果的并集都包含在集合中。
- 如果 E 是逗号表达式 (E1, E2),则 E2 的潜在结果在潜在结果集中。
- 否则,该集合为空。
struct S { static const int a = 1; static const int b = 2; }; int f(bool x) { return x ? S::a : S::b; // x is a part of the subexpression "x" (to the left of ?), // which applies lvalue-to-rvalue conversion, but applying that conversion to x // does not yield a constant expression, so x is odr-used // S::a and S::b are lvalues, and carry over as "potential results" // to the result of the glvalue conditional // That result is then subject to lvalue-to-rvalue conversion requested // to copy-initialize the return value, therefore S::a and S::b are not odr-used }
本节不完整 原因:列出 ODR 使用产生差异的所有情况 |
[编辑] 命名函数
在以下情况下,函数由表达式或转换命名
- 如果函数的名称作为表达式或转换出现(包括命名函数、重载运算符、用户定义的转换、用户定义的放置形式的 operator new、非默认初始化),并且它被重载决议选择,则该函数由该表达式命名,除非它是不合格的纯虚成员函数或指向纯虚成员函数的成员指针。
- 类的 分配 或 释放 函数由表达式中出现的 new 表达式 命名。
- 类的释放函数由表达式中出现的 delete 表达式 命名。
- 即使发生 复制省略,选择用于复制或移动对象的构造函数也被认为是由表达式或转换命名的。在某些情况下使用纯右值不会复制或移动对象,请参阅 强制省略。(自 C++17 起)
如果一个可能求值的表达式或转换命名了一个函数,则它会 ODR 式使用该函数。
命名 constexpr 函数的可能常量求值表达式或转换会使其成为 常量求值所需,这会触发默认函数的定义或函数模板特化的实例化,即使表达式未求值也是如此。 |
(自 C++11 起) |
[编辑] 缺陷报告
以下更改行为的缺陷报告已追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 发布时的行为 | 正确行为 |
---|---|---|---|
CWG 261 | C++98 | 多态类的释放函数 即使程序中没有相关的 new 或 delete 表达式,也可能被 ODR 式使用 |
补充了 ODR 式使用的情况,以涵盖 构造函数和析构函数 |
CWG 678 | C++98 | 一个实体可以具有 具有不同语言链接的定义 |
在这种情况下,行为是 未定义的 |
CWG 1472 | C++98 | 满足出现在常量表达式中的要求的 引用变量即使立即应用了左值到右值的转换也会被 ODR 式使用 |
在这种情况下,它们不会被 ODR 式使用 |
CWG 1614 | C++98 | 获取纯虚函数的地址会 ODR 式使用它 | 该函数未被 ODR 式使用 |
CWG 1741 | C++98 | 在可能求值的表达式中立即进行左值到右值转换的常量对象会被 ODR 式使用 |
在这种情况下,它们不会被 ODR 式使用 |
CWG 1926 | C++98 | 数组下标表达式不会传播潜在结果 | 它们会传播 |
CWG 2242 | C++98 | 尚不清楚在其部分定义中仅进行常量初始化的 const 对象是否违反 ODR |
不违反 ODR;在这种情况下,该对象是 常量初始化的 |
CWG 2300 | C++11 | 不同翻译单元中的 lambda 表达式 永远不可能具有相同的闭包类型 |
在单一定义规则下,闭包类型可以是 相同的 |
CWG 2353 | C++98 | 静态数据成员不是访问它的成员访问表达式的潜在结果 |
它是 |
CWG 2433 | C++14 | 变量模板在程序中不能有多个定义 | 它可以 |
[编辑] 参考文献
- C++23 标准 (ISO/IEC 14882:2024)
- 6.3 单一定义规则 [basic.def.odr]
- C++20 标准 (ISO/IEC 14882:2020)
- 6.3 单一定义规则 [basic.def.odr]
- C++17 标准 (ISO/IEC 14882:2017)
- 6.2 单一定义规则 [basic.def.odr]
- C++14 标准 (ISO/IEC 14882:2014)
- 3.2 单一定义规则 [basic.def.odr]
- C++11 标准 (ISO/IEC 14882:2011)
- 3.2 单一定义规则 [basic.def.odr]
- C++03 标准 (ISO/IEC 14882:2003)
- 3.2 单一定义规则 [basic.def.odr]
- C++98 标准 (ISO/IEC 14882:1998)
- 3.2 单一定义规则 [basic.def.odr]