我了解使用未命名的命名空间使函数和变量具有内部链接。头文件中不使用未命名的命名空间;只有源文件。源文件中声明的类型不能在外部使用。那么将类型放入未命名的命名空间有什么用呢?
请参阅这些链接,其中提到类型可以放在未命名的命名空间中:
我了解使用未命名的命名空间使函数和变量具有内部链接。头文件中不使用未命名的命名空间;只有源文件。源文件中声明的类型不能在外部使用。那么将类型放入未命名的命名空间有什么用呢?
请参阅这些链接,其中提到类型可以放在未命名的命名空间中:
除了未命名的命名空间之外,你想把本地类型放在哪里?类型不能有像static
. 如果它们不为公众所知,例如,因为它们是在标头中声明的,那么本地类型的名称很可能会发生冲突,例如,当两个翻译单元定义具有相同名称的类型时。在这种情况下,您最终会违反 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();
}
如果链接这三个翻译单元,则helper
fromfoo.cpp
和的定义不匹配bar.cpp
。编译器/链接器不需要检测这些,但程序中使用的每种类型都需要具有一致的定义。违反此约束称为违反“单一定义规则”(ODR)。任何违反 ODR 规则的行为都会导致未定义的行为。
鉴于评论似乎需要更有说服力。标准的相关部分是 3.2 [basic.def.odr] 第 6 段:
类类型(第 9 条)、枚举类型(7.2)、带外部链接的内联函数(7.1.2)、类模板(第 14 条)、非静态函数模板(14.5.6)可以有多个定义、类模板的静态数据成员 (14.5.1.3)、类模板的成员函数 (14.5.1.1) 或在程序中未指定某些模板参数的模板特化 (14.7、14.5.5),前提是每个定义出现在不同的翻译单元中,并且定义满足以下要求。给定这样一个名为 D 的实体在多个翻译单元中定义,则 D 的每个定义应由相同的标记序列组成;和 [...]
有很多进一步的限制,但“应由相同的令牌序列组成”显然足以排除例如上述演示中的定义是合法的。
那么将类型放入未命名的命名空间有什么用呢?
您可以创建简短、有意义的类,其名称可能在多个文件中使用,而不会出现名称冲突问题。
例如,我经常在未命名的命名空间中使用两个类 -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 节开始:
每个程序都应包含该程序中 odr 使用的每个非内联函数或变量的准确定义;无需诊断。
如果函数是内联定义的,则链接器不会产生错误:
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”类周围放置一个未命名的命名空间,如下所示:
#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
哇!有用!
事实证明,在未命名的命名空间中定义类非常重要,这样当不同翻译单元中的两个类名相同时,您可以从中获得正确的功能。现在至于为什么会这样,我还没有对此进行任何研究(也许有人可以在这里提供帮助?)所以我不能确定地告诉你。我纯粹从实际的角度来回答。
我怀疑的是,虽然C 结构确实是翻译单元的本地,但它们与类有点不同,因为 C++ 中的类通常具有分配给它们的行为。行为意味着函数,正如我们所知,函数不是翻译单元的本地函数。
这只是我的假设。