这个问题建立在这两个 stackoverflow 帖子的基础上:
问题是:为什么类/结构/枚举不会出现多重定义错误?为什么它只适用于函数或变量?
我编写了一些示例代码来试图解决我的困惑。有 4 个文件:namespace.h、test.h、test.cpp 和 main.cpp。第一个文件包含在 test.cpp 和 main.cpp 中,如果正确的行未注释,则会导致多重定义错误。
// namespace.h
#ifndef NAMESPACE_H
#define NAMESPACE_H
namespace NamespaceTest {
// 1. Function in namespace: must be declaration, not defintion
int test(); // GOOD
// int test() { // BAD
// return 5;
//}
// 2. Classes can live in header file with full implementation
// But if the function is defined outside of the struct, it causes compiler error
struct TestStruct {
int x;
int test() { return 10; } // GOOD
};
//int TestStruct::test() { // BAD
// return 10;
//}
// 3. Variables are also not spared from the multiple definition error.
//int x = 20; // BAD
// 4. But enums are perfectly safe.
enum TestEnum { ONE, TWO }; // GOOD
}
#endif
// test.h
#ifndef TEST_H
#define TEST_H
class Test {
public:
int test();
};
#endif
// test.cpp
#include "test.h"
#include "namespace.h"
int NamespaceTest::test() {
return 5;
}
int Test::test() {
return NamespaceTest::test() + 1;
}
// main.cpp
#include <iostream>
#include "namespace.h"
#include "test.h"
int main() {
std::cout << "NamespaceTest::test: " << NamespaceTest::test() << std::endl;
Test test;
std::cout << "Test::test: " <<test.test() << std::endl;
NamespaceTest::TestStruct test2;
std::cout << "NamespaceTest::TestStruct::test: " << test2.test() << std::endl;
std::cout << "NamespaceTest::x: " << NamespaceTest::TestEnum::ONE << std::endl;
}
g++ test.cpp main.cpp -o main.out && ./main.out
NamespaceTest::test: 5
Test::test: 6
NamespaceTest::TestStruct::test: 10
NamespaceTest::x: 0
阅读完cppreference:内联说明符后,我有了部分答案。内联规则规定类内定义的函数被视为内联。内联函数允许有重复的定义,前提是 (1) 它们位于不同的翻译单元中并且 (2) 相同。我是在转述,但这就是要点。
这解释了为什么函数是合法的,但不能解释为什么类或枚举的多个定义是可以的。我想象可能有类似的解释,但最好能确定一下。
通常,当您编译名称空间范围内的定义(如函数或全局变量)时,编译器将为它发出一个全局符号。如果这出现在多个翻译单元中,则在链接时会出现冲突,因为有多个定义(恰好是等效的,但链接器无法检查这一点)。
这是一个定义规则的一部分:在整个程序中,在一个翻译单元中,只允许对函数或变量进行一个定义。
有一些例外,例如类定义和内联函数/变量。但是,定义在它们出现的所有翻译单元中必须完全相同(文本上)。类定义意味着
#include
d,因此允许它们出现在多个翻译单元中是有意义的。
inline
,因为否则您将无法在不破坏 ODR 的情况下将类定义包含在成员函数定义中。例如,这三个在功能上是等效的:
struct TestStruct {
int x;
int test() { return 10; }
};
// Could have been written
struct TestStruct {
int x;
inline int test() { return 10; }
};
// Or as
struct TestStruct {
int x;
int test(); // The `inline` specifier could also be here
};
inline int TestStruct::test() { return 10; }
您也可以对命名空间作用域的函数/变量执行此操作:
inline int test() { return 5; }
和inline int x = 20;
将编译而不会再出现任何问题。
这是通过编译器为内联实体发出“特殊标记”符号来实现的,链接器任意选择一个符号,因为它们应该都是相同的。
模板化函数/变量和枚举声明也存在 ODR 的相同例外,因为它们也应该存在于头文件中。
我的解释
为什么类/结构/枚举没有出现多重定义错误?为什么它只适用于函数或变量?
也就是说,函数和变量都有分配给它们的特定内存区域,链接器通过它们的符号名称引用该内存区域。例如,函数是 .text 区域中的一段代码。或者一个变量可能存在于.bss等中。因此,它们不应有多个定义。但类/结构/枚举定义只是一个布局、一个计划、一个内存块组织的定义。