问题 将函数定义放在头文件中


如果要将函数定义放在头文件中,则会出现三种不同的解决方案:

  1. 将功能标记为 inline
  2. 将功能标记为 static
  3. 将该函数放在匿名命名空间中

(直到最近,我甚至都没有意识到#1。)那么这些解决方案有什么不同之处,何时我应该选择哪种?我是在仅限标题的世界中,所以我真的需要头文件中的定义。


9294
2017-10-20 09:37


起源

你忘了:把它们变成功能模板。这就是我通常喜欢的。 - sbi
不要使用静电导致许多不同的副本和疯狂。 - Martin York
“每个匿名命名空间中的一个副本”解决方案导致同样的疯狂。 - MSalters


答案:


static 和未命名的命名空间版本最终是相同的:每个翻译单元将包含它自己的函数版本,这意味着给定一个静态函数 f,指针 &f 每个翻译单元会有所不同,程序将包含N个不同版本的 f (二进制代码中的更多代码)。

这是  提供正确的方法 一个 函数在标题中,它将提供 ñ 不同(完全相同)的功能。如果函数包含 static 然后当地人会有 ñ 不同 static 局部变量......

编辑:为了使它更明确:如果你想要的是在不破坏一个定义规则的情况下在头中提供函数的定义,正确的方法是使函数成为函数 inline


12
2017-10-20 10:24



所以 inline 是正确的方法吗?如果你明确这样做会很好。 - fredoverflow
我对此的理解是相似的,我发布了相同的答案但后来我删除了它,因为我有点坚持Q, How making a function defined in header as inline,makes it bypass the ODR?, 我想 Section $3.2/5 解决了这个问题,但我不确定。这可能比Q中所要求的更详细,但是请你详细说明。 - Alok Save
@Als:3.2 / 3 每个程序应该只包含该程序中使用的每个非内联函数或对象的一个​​定义;无需诊断。 [...]应在每个使用它的翻译单元中定义内联函数。 (来自C ++ 03的措辞,但同样可以在C ++ 11中找到 用过的 是 ODR使用的) - David Rodríguez - dribeas
@DavidRodríguez-dribeas:+ 1,这总结了我。谢谢:) - Alok Save


据我所知,只有 inline 和模板函数可以在头文件中定义。

static 不推荐使用函数,而应使用未命名的命名空间中定义的函数(参见7.3.1.1 p2)。当您在标头中的未命名命名空间中定义函数时,包含该标头(直接或间接)的每个源代码都将具有唯一的定义(请参阅7.3.1.1 p1)。因此,不应在头文件中的未命名命名空间中定义函数(仅在源文件中)。

引用的标准来自c ++ 03标准。

编辑:

下一个示例演示了为什么函数和变量不应该在头文件中定义到未命名的命名空间中:

ops.hpp 包含:

#ifndef OPS_HPP
#define OPS_HPP
namespace
{
int a;
}
#endif

dk1.hpp 包含:

#ifndef DK1_HPP
#define DK1_HPP
void setValue();
void printValue();
#endif

dk1.cpp 包含:

#include "dk1.hpp"
#include "ops.hpp"
#include <iostream>

void setValue()
{
    a=5;
}
void printValue()
{
    std::cout<<a<<std::endl;
}

dk.cpp 包含:

#include "dk1.hpp"
#include "ops.hpp"
#include <iostream>

int main()
{
    // set and print a
    setValue();
    printValue();

    // set and print it again
    a = 22;
    std::cout<<a<<std::endl;

    // print it again
    printValue();
}

编译如下:

g++ -ansi -pedantic -Wall -Wextra dk.cpp dk1.cpp

和输出:

5
22
5

选择变量 a 与源文件不同 dk1.cpp 和 dk.cpp


4
2017-10-20 10:17



在头文件中使用带有函数定义的匿名命名空间本身没有任何错误 - 它不违反ODR - Flexo♦
static 函数永远不会被弃用 static 对象(在C ++ 11之前)。 - Mike Seymour
@awoodland:从编译器的角度来看,没有任何内在错误,但最有可能的是从程序的角度来看。看起来像什么 一个 实际上是标题中的函数 许多 在不同的翻译单元中,它将生成额外的代码(更大的二进制,更糟糕的指令缓存性能),如果函数包含 本地静态 变量,每个翻译单元都会引用它自己的版本。编译器会这样做并且很开心。但无论谁将来需要调试它都不会那么开心。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas - 这是一个有趣的决定,你有没有参考这个逆转背后的理由? - Flexo♦
@awoodland:是的,但它会使代码膨胀(通过生成每个函数的多个单独副本),并且当函数中的静态数据不在翻译单元之间共享时可能导致细微的错误。 - Mike Seymour


static 函数(相当于匿名命名空间)为每个TU接收不同的副本。如果函数是可重入的,则这基本相同(某些编译器可能具有汇编级差异),但如果不是,那么每个TU将具有不同的静态数据。内联函数是折叠的 - 也就是说,它们每个TU只有一个静态数据副本。


0
2017-10-20 09:39



@TonyK:虽然我同意不使用缩写和使用完整符号的建议,但我强烈反对 OP obviously isn't an expertOP是SO中为数不多的能够真正理解C ++核心的专家之一。 - Alok Save
好吧,我决定删除我的评论,但为时已晚......所以让我在这里重申一下:使用像TU这样的缩写假设你的读者有一定程度的经验,这使你的解释变得多余。 (除非,显然,你的读者是FredOverflow。)而BTW,它意味着翻译单位。 - TonyK