为什么在头文件中定义函数会产生多个定义错误,但不会 类?

Why does defining functions in header files create multiple definition errors, but not classes?

这个问题基于这两个 Whosebug 帖子:

问题来了:为什么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 定义意味着 #included,因此允许它们出现在多个翻译单元中是有意义的.

如果您在 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 的相同例外,因为它们也应该存在于头文件中。