为什么在头文件中定义函数会产生多个定义错误,但不会 类?
Why does defining functions in header files create multiple definition errors, but not classes?
这个问题基于这两个 Whosebug 帖子:
- multiple definition in header file
问题来了:为什么classes/structs/enums没有出现多重定义错误?为什么它只适用于函数或变量?
我写了一些示例代码来解决我的困惑。有 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: inline specifier,我有了部分答案。内联规则规定在 class 内定义的函数被认为是内联的。并且内联函数允许有重复的定义,前提是 (1) 它们存在于不同的翻译单元中,并且 (2) 是相同的。我在解释,但这是要点。
这解释了为什么函数是合法的,但不能解释为什么 class 或枚举的多个定义是可以的。可能是我想象的类似解释,但最好能确定。
通常,当您编译一个命名空间作用域的定义(如函数或全局变量)时,您的编译器会为它生成一个全局符号。如果这出现在多个翻译单元中,在 link-time 期间会发生冲突,因为有多个定义(恰好是等价的,但 linker 无法检查)。
这是 one definition rule 的一部分:在一个翻译单元中,整个程序中只允许定义一个函数或变量。
这有一些例外,例如,class 定义和内联 functions/variables。但是,定义在它们出现的所有翻译单元中必须完全相同(在文本上)。Class 定义意味着 #include
d,因此允许它们出现在多个翻译单元中是有意义的.
如果您在 class 主体内定义成员函数,则它们隐含地 inline
因为否则您将无法在不中断的情况下将 class 定义包含在成员函数定义中在线解决方案。例如,这三个在功能上是等价的:
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; }
您也可以对 functions/variables 范围内的名称空间执行此操作:inline int test() { return 5; }
和 inline int x = 20;
将不会再出现问题。
这是由编译器为内联实体发出“特殊标记”的符号实现的,linker 任意选择一个,因为它们应该都是相同的。
模板化函数/变量和枚举声明也存在 ODR 的相同例外,因为它们也应该存在于头文件中。
这个问题基于这两个 Whosebug 帖子:
- multiple definition in header file
问题来了:为什么classes/structs/enums没有出现多重定义错误?为什么它只适用于函数或变量?
我写了一些示例代码来解决我的困惑。有 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: inline specifier,我有了部分答案。内联规则规定在 class 内定义的函数被认为是内联的。并且内联函数允许有重复的定义,前提是 (1) 它们存在于不同的翻译单元中,并且 (2) 是相同的。我在解释,但这是要点。
这解释了为什么函数是合法的,但不能解释为什么 class 或枚举的多个定义是可以的。可能是我想象的类似解释,但最好能确定。
通常,当您编译一个命名空间作用域的定义(如函数或全局变量)时,您的编译器会为它生成一个全局符号。如果这出现在多个翻译单元中,在 link-time 期间会发生冲突,因为有多个定义(恰好是等价的,但 linker 无法检查)。
这是 one definition rule 的一部分:在一个翻译单元中,整个程序中只允许定义一个函数或变量。
这有一些例外,例如,class 定义和内联 functions/variables。但是,定义在它们出现的所有翻译单元中必须完全相同(在文本上)。Class 定义意味着 #include
d,因此允许它们出现在多个翻译单元中是有意义的.
如果您在 class 主体内定义成员函数,则它们隐含地 inline
因为否则您将无法在不中断的情况下将 class 定义包含在成员函数定义中在线解决方案。例如,这三个在功能上是等价的:
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; }
您也可以对 functions/variables 范围内的名称空间执行此操作:inline int test() { return 5; }
和 inline int x = 20;
将不会再出现问题。
这是由编译器为内联实体发出“特殊标记”的符号实现的,linker 任意选择一个,因为它们应该都是相同的。
模板化函数/变量和枚举声明也存在 ODR 的相同例外,因为它们也应该存在于头文件中。