C++ 全局头函数类型解释
C++ Types of global header functions explained
目前我想在头文件中包含一个全局函数。我找到了三种可能的实现方式(可能还有更多)。
第一个是有一个 class 有一个 public 静态函数:
class foo {
public:
static void bar() {
...
}
};
第二个是内联全局函数:
inline void bar() {
...
}
第三种是使用C风格的静态头函数:
static void bar() {
...
}
我想知道是否有一种普遍接受的在头文件中包含全局函数的正确方法,以及上述方法的优缺点。
第一种方法(class 中的 static
成员函数)的替代方法是使用命名空间:
namespace foo {
inline void bar () {
...
}
}
如果 class foo
的每个成员都是静态的,则最好使用命名空间方法,因为后者 (namespace foo
) 可以更好地显示意图。
最后一种方法(使用 C 风格的静态函数)的替代方法是使用匿名命名空间:
namespace {
void bar () {
...
}
}
这两种方法(C 风格的静态函数与匿名命名空间)中哪一种更好是一个见仁见智的问题。在 C++11 之前,不推荐在命名空间范围内使用 static
,包括全局命名空间。
所以现在我们有五种方法,而不是问题中列出的三种方法。
使函数 inline
(您的前两个选项,加上我的命名空间方法)意味着在可执行文件中最多会有一个函数定义。 inline
关键字是对实现的弱提示,它应该考虑在调用函数的地方内联扩展相关函数。另一方面,inline
关键字对于实现单一定义规则是一个非常强大的命令。 inline
在这种情况下的意思是,它是对实现的承诺,即虽然在不同的翻译单元中可能有多个定义,但这些定义中的每一个都将是彼此完全相同的副本。
如果您正在使用编译器和链接器,编译器可能会拒绝服从您的 inline
提示,而是定义函数外联。此外联定义将对链接器可见。链接器会将不同翻译单元中的多个定义剔除为生成的可执行文件中的一个定义。
假设您的函数定义涉及宏,其扩展因一个翻译单元而异。或者假设您已经编译了不同的翻译单元,这些翻译单元在不同的优化级别使用相同的函数,使得函数的编译版本在不同的翻译单元中不同。无论哪种方式,您都违反了对跨翻译单元的函数的多个定义确实相同的实现的承诺。实施不需要对此进行检查,"no diagnostic required"。该实现将任意选择这些定义之一作为要在整个可执行文件中使用的一个定义。
使函数 static
(通过您的第三个选项显式地,或通过我的匿名名称空间选项隐式地)隐藏链接器的定义。如果您在多个翻译单元中使用该函数,则可执行文件将具有本质上相同的函数的多个实现。
使用内部链接方法(C 风格的静态函数或匿名命名空间)避免了上述问题,因为这里的单一定义规则适用于翻译单元级别而不是可执行级别。这种方法可用于确保一个翻译单元使用函数的高度优化版本,而另一个翻译单元使用同一函数的非优化、调试友好版本。如果所有定义确实相同,缺点是可执行文件臃肿。
目前我想在头文件中包含一个全局函数。我找到了三种可能的实现方式(可能还有更多)。
第一个是有一个 class 有一个 public 静态函数:
class foo {
public:
static void bar() {
...
}
};
第二个是内联全局函数:
inline void bar() {
...
}
第三种是使用C风格的静态头函数:
static void bar() {
...
}
我想知道是否有一种普遍接受的在头文件中包含全局函数的正确方法,以及上述方法的优缺点。
第一种方法(class 中的 static
成员函数)的替代方法是使用命名空间:
namespace foo {
inline void bar () {
...
}
}
如果 class foo
的每个成员都是静态的,则最好使用命名空间方法,因为后者 (namespace foo
) 可以更好地显示意图。
最后一种方法(使用 C 风格的静态函数)的替代方法是使用匿名命名空间:
namespace {
void bar () {
...
}
}
这两种方法(C 风格的静态函数与匿名命名空间)中哪一种更好是一个见仁见智的问题。在 C++11 之前,不推荐在命名空间范围内使用 static
,包括全局命名空间。
所以现在我们有五种方法,而不是问题中列出的三种方法。
使函数 inline
(您的前两个选项,加上我的命名空间方法)意味着在可执行文件中最多会有一个函数定义。 inline
关键字是对实现的弱提示,它应该考虑在调用函数的地方内联扩展相关函数。另一方面,inline
关键字对于实现单一定义规则是一个非常强大的命令。 inline
在这种情况下的意思是,它是对实现的承诺,即虽然在不同的翻译单元中可能有多个定义,但这些定义中的每一个都将是彼此完全相同的副本。
如果您正在使用编译器和链接器,编译器可能会拒绝服从您的 inline
提示,而是定义函数外联。此外联定义将对链接器可见。链接器会将不同翻译单元中的多个定义剔除为生成的可执行文件中的一个定义。
假设您的函数定义涉及宏,其扩展因一个翻译单元而异。或者假设您已经编译了不同的翻译单元,这些翻译单元在不同的优化级别使用相同的函数,使得函数的编译版本在不同的翻译单元中不同。无论哪种方式,您都违反了对跨翻译单元的函数的多个定义确实相同的实现的承诺。实施不需要对此进行检查,"no diagnostic required"。该实现将任意选择这些定义之一作为要在整个可执行文件中使用的一个定义。
使函数 static
(通过您的第三个选项显式地,或通过我的匿名名称空间选项隐式地)隐藏链接器的定义。如果您在多个翻译单元中使用该函数,则可执行文件将具有本质上相同的函数的多个实现。
使用内部链接方法(C 风格的静态函数或匿名命名空间)避免了上述问题,因为这里的单一定义规则适用于翻译单元级别而不是可执行级别。这种方法可用于确保一个翻译单元使用函数的高度优化版本,而另一个翻译单元使用同一函数的非优化、调试友好版本。如果所有定义确实相同,缺点是可执行文件臃肿。