为什么类型应该放在未命名的命名空间中?
Why should types be put in unnamed namespaces?
我了解使用未命名的命名空间来使函数和变量具有内部链接。头文件中不使用未命名的命名空间;只有源文件。源文件中声明的类型不能在外部使用。那么将类型放在未命名的命名空间中有什么用呢?
查看这些链接,其中提到类型可以放在未命名的命名空间中:
- Superiority of unnamed namespace over static?
- Unnamed/anonymous namespaces vs. static functions
- Why an unnamed namespace is a "superior" alternative to static?
除未命名命名空间外,您想将本地类型放在哪里?类型不能有像 static
这样的 linkage 说明符。如果它们不是公开的,例如,因为它们是在 header 中声明的,本地类型的名称很可能会发生冲突,例如,当两个翻译单元定义具有相同名称的类型时。在这种情况下,您最终会违反 ODR。在未命名的命名空间中定义类型消除了这种可能性。
更具体一点。假设你有
// file demo.h
int foo();
double bar();
// file foo.cpp
struct helper { int i; };
int foo() { helper h{}; return h.i; }
// file bar.cpp
struct helper { double d; }
double bar() { helper h{}; return h.d; }
// file main.cpp
#include "demo.h"
int main() {
return foo() + bar();
}
如果您 link 这三个翻译单元,您的 helper
来自 foo.cpp
和 bar.cpp
的定义不匹配。 compiler/linker 不需要检测这些,但程序中使用的每种类型都需要具有一致的定义。违反此约束称为违反 "one definition rule" (ODR)。任何违反 ODR 规则的行为都会导致未定义的行为。
鉴于评论,似乎需要更有说服力。标准的相关部分是 3.2 [basic.def.odr] 第 6 段:
There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member
of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then each definition of D shall consist of the same sequence of tokens; and
[...]
还有很多进一步的限制,但 "shall consist of the same sequence of tokens" 显然足以排除例如上面演示中的定义是合法的。
So what's the use of putting types in unnamed namespaces?
您可以创建简短、有意义的 类 名称,这些名称可能会在多个文件中使用,而不会出现名称冲突问题。
例如,我经常在未命名的命名空间中使用两个 类 - Initializer
和 Helper
.
namespace
{
struct Initializer
{
Initializer()
{
// Take care of things that need to be initialized at static
// initialization time.
}
};
struct Helper
{
// Provide functions that are useful for the implementation
// but not exposed to the users of the main interface.
};
// Take care of things that need to be initialized at static
// initialization time.
Initializer initializer;
}
我可以在任意多个文件中重复这种代码模式,而不会受到名称 Initializer
和 Helper
的阻碍。
更新,回应 OP
的评论
文件-1.cpp:
struct Initializer
{
Initializer();
};
Initializer::Initializer()
{
}
int main()
{
Initializer init;
}
文件-2.cpp:
struct Initializer
{
Initializer();
};
Initializer::Initializer()
{
}
构建命令:
g++ file-1.cpp file-2.cpp
我收到有关 Initializer::Initializer()
的多个定义的链接器错误消息。请注意,标准不要求链接器产生此错误。来自第 3.2/4 节:
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.
如果函数是内联定义的,链接器不会产生错误:
struct Initializer
{
Initializer() {}
};
对于像这样的简单案例来说没关系,因为实现是相同的。如果内联实现不同,则程序会出现未定义的行为。
我回答 OP 提出的问题可能有点晚了,但由于我认为答案并不完全清楚,我想帮助未来的读者。
让我们试一试...编译以下文件:
//main.cpp
#include <iostream>
#include "test.hpp"
class Test {
public:
void talk() {
std::cout<<"I'm test MAIN\n";
}
};
int main()
{
Test t;
t.talk();
testfunc();
}
//test.hpp
void testfunc();
//test.cpp
#include <iostream>
class Test {
public:
void talk()
{
std::cout<<"I'm test 2\n";
}
};
void testfunc() {
Test t;
t.talk();
}
现在 运行 可执行文件。
您希望看到:
I'm test MAIN
I'm test 2
你应该看到的想法是:
I'm test MAIN
I'm test MAIN
发生了什么事?!?!!
现在尝试在 "test.cpp" 中的 "Test" class 周围放置一个未命名的命名空间,如下所示:
#include <iostream>
#include "test.hpp"
namespace{
class Test {
public:
void talk()
{
std::cout<<"I'm test 2\n";
}
};
}
void testfunc() {
Test t;
t.talk();
}
再次编译并运行。
输出应该是:
I'm test MAIN
I'm test 2
哇!有效!
事实证明,在未命名的命名空间中定义 classes 很重要,这样当两个 class 不同翻译单元中的名称是相同的。
现在至于 为什么 是这样,我还没有对此进行任何研究(也许有人可以在这里提供帮助?)所以我不能确定地告诉你。我纯粹从实际的角度回答。
不过我怀疑的是,虽然 true C 结构确实是翻译单元的本地结构,但它们与 classes 有点不同因为 c++ 中的 classes 通常具有分配给它们的行为。行为意味着函数,正如我们所知,函数不是翻译单元的本地函数。
这只是我的假设。
我了解使用未命名的命名空间来使函数和变量具有内部链接。头文件中不使用未命名的命名空间;只有源文件。源文件中声明的类型不能在外部使用。那么将类型放在未命名的命名空间中有什么用呢?
查看这些链接,其中提到类型可以放在未命名的命名空间中:
- Superiority of unnamed namespace over static?
- Unnamed/anonymous namespaces vs. static functions
- Why an unnamed namespace is a "superior" alternative to static?
除未命名命名空间外,您想将本地类型放在哪里?类型不能有像 static
这样的 linkage 说明符。如果它们不是公开的,例如,因为它们是在 header 中声明的,本地类型的名称很可能会发生冲突,例如,当两个翻译单元定义具有相同名称的类型时。在这种情况下,您最终会违反 ODR。在未命名的命名空间中定义类型消除了这种可能性。
更具体一点。假设你有
// file demo.h
int foo();
double bar();
// file foo.cpp
struct helper { int i; };
int foo() { helper h{}; return h.i; }
// file bar.cpp
struct helper { double d; }
double bar() { helper h{}; return h.d; }
// file main.cpp
#include "demo.h"
int main() {
return foo() + bar();
}
如果您 link 这三个翻译单元,您的 helper
来自 foo.cpp
和 bar.cpp
的定义不匹配。 compiler/linker 不需要检测这些,但程序中使用的每种类型都需要具有一致的定义。违反此约束称为违反 "one definition rule" (ODR)。任何违反 ODR 规则的行为都会导致未定义的行为。
鉴于评论,似乎需要更有说服力。标准的相关部分是 3.2 [basic.def.odr] 第 6 段:
There can be more than one definition of a class type (Clause 9), enumeration type (7.2), inline function with external linkage (7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then each definition of D shall consist of the same sequence of tokens; and [...]
还有很多进一步的限制,但 "shall consist of the same sequence of tokens" 显然足以排除例如上面演示中的定义是合法的。
So what's the use of putting types in unnamed namespaces?
您可以创建简短、有意义的 类 名称,这些名称可能会在多个文件中使用,而不会出现名称冲突问题。
例如,我经常在未命名的命名空间中使用两个 类 - Initializer
和 Helper
.
namespace
{
struct Initializer
{
Initializer()
{
// Take care of things that need to be initialized at static
// initialization time.
}
};
struct Helper
{
// Provide functions that are useful for the implementation
// but not exposed to the users of the main interface.
};
// Take care of things that need to be initialized at static
// initialization time.
Initializer initializer;
}
我可以在任意多个文件中重复这种代码模式,而不会受到名称 Initializer
和 Helper
的阻碍。
更新,回应 OP
的评论文件-1.cpp:
struct Initializer
{
Initializer();
};
Initializer::Initializer()
{
}
int main()
{
Initializer init;
}
文件-2.cpp:
struct Initializer
{
Initializer();
};
Initializer::Initializer()
{
}
构建命令:
g++ file-1.cpp file-2.cpp
我收到有关 Initializer::Initializer()
的多个定义的链接器错误消息。请注意,标准不要求链接器产生此错误。来自第 3.2/4 节:
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.
如果函数是内联定义的,链接器不会产生错误:
struct Initializer
{
Initializer() {}
};
对于像这样的简单案例来说没关系,因为实现是相同的。如果内联实现不同,则程序会出现未定义的行为。
我回答 OP 提出的问题可能有点晚了,但由于我认为答案并不完全清楚,我想帮助未来的读者。
让我们试一试...编译以下文件:
//main.cpp
#include <iostream>
#include "test.hpp"
class Test {
public:
void talk() {
std::cout<<"I'm test MAIN\n";
}
};
int main()
{
Test t;
t.talk();
testfunc();
}
//test.hpp
void testfunc();
//test.cpp
#include <iostream>
class Test {
public:
void talk()
{
std::cout<<"I'm test 2\n";
}
};
void testfunc() {
Test t;
t.talk();
}
现在 运行 可执行文件。 您希望看到:
I'm test MAIN
I'm test 2
你应该看到的想法是:
I'm test MAIN
I'm test MAIN
发生了什么事?!?!!
现在尝试在 "test.cpp" 中的 "Test" class 周围放置一个未命名的命名空间,如下所示:
#include <iostream>
#include "test.hpp"
namespace{
class Test {
public:
void talk()
{
std::cout<<"I'm test 2\n";
}
};
}
void testfunc() {
Test t;
t.talk();
}
再次编译并运行。 输出应该是:
I'm test MAIN
I'm test 2
哇!有效!
事实证明,在未命名的命名空间中定义 classes 很重要,这样当两个 class 不同翻译单元中的名称是相同的。 现在至于 为什么 是这样,我还没有对此进行任何研究(也许有人可以在这里提供帮助?)所以我不能确定地告诉你。我纯粹从实际的角度回答。
不过我怀疑的是,虽然 true C 结构确实是翻译单元的本地结构,但它们与 classes 有点不同因为 c++ 中的 classes 通常具有分配给它们的行为。行为意味着函数,正如我们所知,函数不是翻译单元的本地函数。
这只是我的假设。