一个内联函数可以在程序中定义多次,只要定义相同
不。(“相同”在这里甚至不是一个明确定义的概念。)
形式上,定义必须在某种非常强烈的意义上是等价的,这甚至没有任何意义作为要求,也没有人关心:
// in some header (included in multiple TU):
const int limit_max = 200; // implicitly static
inline bool check_limit(int i) {
return i<=limit_max; // OK
}
inline int impose_limit(int i) {
return std::min(i, limit_max); // ODR violation
}
这样的代码是完全合理的,但在形式上违反了单一定义规则:
在 D 的每个定义中,对应的名称,根据 6.4 [basic.lookup] 查找,应指在 D 的定义中定义的实体,或应指同一实体,在重载决议(16.3 [over.match] ) 并在匹配部分模板特化 (17.9.3 [temp.over]) 之后,除了如果对象在 D 的所有定义中具有相同的文字类型,则名称可以引用具有内部链接或没有链接的 const 对象,并且用常量表达式(8.20 [expr.const])初始化对象,并使用对象的值(但不是地址),并且对象在D的所有定义中具有相同的值;
因为异常不允许使用具有内部链接的 const 对象(const int
隐式静态)来直接绑定 const 引用(然后仅将引用用作其值)。正确的版本是:
inline int impose_limit(int i) {
return std::min(i, +limit_max); // OK
}
这里的值limit_max
在一元运算符 + 中使用,然后将 const 引用绑定到使用该值初始化的临时对象。谁真的这样做?
但即使是委员会也不相信正式的 ODR 很重要,正如我们在核心问题 1511中看到的那样:
1511. const volatile 变量和单定义规则
部分:6.2 [basic.def.odr] 状态:CD3 提交者:Richard Smith 日期:2012-06-18
[在 2013 年 4 月的会议上移至 DR。]
对于以下示例,此措辞可能不够清楚:
const volatile int n = 0;
inline int get() { return n; }
我们看到,委员会认为这种公然违反 ODR 的意图和目的的行为,即在每个 TU 中读取不同易失性对象的代码,即对不同对象具有可见副作用的代码,因此不同的可见副作用,没关系,因为我们不在乎哪个是哪个。
重要的是内联函数的效果是模糊等价的:执行 volatile int 读取,这是一个非常弱的等价,但足以自然使用ODR,即实例无差异:使用内联函数的哪个特定实例没关系,也不能有所作为。
特别是 volatile 读取的值根据定义编译器不知道,因此编译器分析的该函数的后置条件和不变量是相同的。
在不同的 TU 中使用不同的函数定义时,您需要确保从调用者的角度来看它们是严格等价的:永远不可能通过用一个替换另一个来让调用者感到惊讶。这意味着即使代码不同,可观察的行为也必须严格相同。
如果您使用不同的编译器选项,它们不得更改函数可能结果的范围(编译器认为可能)。
因为“标准”(实际上并不是编程语言的规范)允许浮点对象具有其官方声明类型所不允许的真实表示,以完全不受约束的方式,使用任何非 volatile 限定的浮点类型任何受 ODR 多重定义的东西似乎都有问题,除非您激活“double
手段double
”模式(这是唯一理智的模式)。