【问题标题】:Should I protect against ODR violations when declaring function templates in source files?在源文件中声明函数模板时是否应该防止 ODR 违规?
【发布时间】:2021-01-07 15:17:46
【问题描述】:

一个“普通”函数,当在单个翻译单元中单独定义和使用时,声明和定义如下:

// implementation.cpp
static void fun(int arg) { /* implementation */ }

或者,您可以删除 static 关键字并将函数包装在 未命名的命名空间中。但如果不执行上述任一操作可能会导致 ODR 违规:如果不同的翻译单元也具有 fun 的声明/定义(具有相同的参数),则喜欢者可能不得不默默地丢弃一个的实现以保持单一定义(在这种情况下,您最好为两者拼写完全相同的定义,否则您会看到意外行为)。

如上所述,问题更“面向实现”,但即使是标准也会敦促您使用staticnamespace { /**/ } 来防止此类副作用。

问题是当fun函数模板时会发生什么?我是否还需要将其包装在未命名的命名空间中或在翻译单元中将其声明为静态?

template <class T>
/* static ? */ void fun(T arg) { /*...*/ }

cppreference 中提到,如果函数是 inline 显然没问题(对单一定义的要求仅针对非内联函数提出),因此无需担心 ODR:

每个非内联函数的一个且只有一个定义或 odr 使用的变量(见下文)必须出现在 整个程序

我们知道函数模板是内联的,但这足以保证 ODR 的正确性吗?

【问题讨论】:

  • 您的报价是非内联函数和变量的单一定义规则。请继续阅读后面的段落,了解内联函数的要求。 (不,定义不会消失。inline 关键字与直接使用函数内容无关,也就是内联函数。)
  • @JaMiT 所以我应该使用静态还是将其放在未命名的命名空间中? (我将编辑引用之前的文本,以更好地连接 inline 和 non 之间的点)
  • 您可以拥有任意数量的模板实例。链接器将占用一个,一切正常。我们在不同的编译单元中实例化多个实例是正常的用例。这种情况没什么特别的。
  • @Klaus 显然,当另一位作者尝试在不同的翻译单元中将fun 声明为其他内容时,问题就会出现。这也是在非模板情况下发生的情况(如果我们有一个简单的情况,只有一个 main.cpp 没有多少人使用未命名的命名空间)。我不是在询问多个实例化,但最佳实践是什么,我应该主动将其声明为静态吗?如果不同的翻译单元包含`template fun(T arg) { /* 这里不同的实现*/ } 会不会有(n ODR) 问题?
  • 好的,你的意思是说:“其他人”将在不同的文件中创建一个具有相同签名的模板函数,并在不同的编译单元中使用它。好的。但是,如果您担心这些事情,您也应该担心其他人也会创建具有不同内容的命名空间并添加其他违反 ODR 的内容。抱歉,但这看起来不像是技术问题,而是与项目管理、审查和文档有关的问题。如果发生这种情况,例如,您如何定义包含订单?只有我的两分钱

标签: c++ templates one-definition-rule


【解决方案1】:

cppreference 中提到,如果一个函数(带有external linkage)被标记为inline,那么必须格外小心,因为该定义必须存在于每个使用该函数的翻译单元中。当有多个定义时,必须满足几个条件,包括:

每个定义都由相同的标记序列组成

如果不满足此要求,则程序格式错误,不需要诊断。此要求同样适用于内联函数和函数模板。有时这会成功,但如果编译器选择不内联对您的函数的调用(inline 关键字不会影响这一点),您最终可能会调用错误版本的函数。链接器不需要检测到这一点。

事实上,this 是链接器倾向于默默忽略的 ODR 违规。尽管问题中声称,当有多个定义具有外部链接的非内联函数时,链接器往往会产生错误并中止。

注意:标记一个函数 static 或将其包含在匿名命名空间中将导致它不再具有外部链接。


我想指出,这个问题以一种相当常见但非标准的方式使用“显然”这个词,意思是“我需要以下虚假陈述为真,所以毫无疑问地接受它。”每当您觉得需要证明某件事“显而易见”时,学会停下来质疑自己是很有用的。

【讨论】:

  • 该问题与定义不存在无关,与定义可见/可用多次有关。我不担心我的翻译单元无法找到其中声明的函数;我担心未来的开发可能会破坏我的程序,因为不同的作者决定声明一个恰好具有相同名称的不同函数模板,而我们都不愿意使用staticunnamed namespace
  • “我不担心我的翻译单元找不到它里面声明的函数”——你应该是。如果其他作者决定声明一个恰好具有相同名称的不同函数模板,您的翻译单元可能最终会调用其他作者的模板。
  • cppreference 表示,当fun 是内联并构成“完全一个”的定义仅在非内联函数上。为什么?我的猜测是因为当一个函数是内联的时,你无法在目标文件中找到它的符号,因为它的内容是直接使用的,所以链接器不用担心丢弃其中一个定义
  • @LorahAttkins 仔细检查您正在阅读的页面。这可能是 C 的规则,而不是 C++ 的规则。
  • 感谢您为此付出的努力,但我问的是完全不同的事情。函数模板是否需要普通函数的常见做法(在 cpp 文件中将它们声明为静态)?就是这样……
猜你喜欢
  • 2018-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-19
  • 1970-01-01
相关资源
最近更新 更多