简体   繁体   English

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

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

This question builds off these two stackoverflow posts:这个问题建立在这两个stackoverflow帖子之上:

Here's the question: Why doesn't the multiple definition error appear for classes/structs/enums?这里的问题是:为什么类/结构/枚举不会出现多重定义错误? Why does it only apply to functions or variables?为什么它只适用于函数或变量?

I wrote some example code in an effort to capture my confusion.我写了一些示例代码来解决我的困惑。 There are 4 files: namespace.h, test.h, test.cpp, and main.cpp.有 4 个文件:namespace.h、test.h、test.cpp 和 main.cpp。 The first file is included in both test.cpp and main.cpp, which leads to the multiple definition error if the correct lines are uncommented.第一个文件包含在 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

After reading cppreference: inline specifier , I have a partial answer.阅读cppreference: inline specifier 后,我有部分答案。 The rules for inline stipulate that functions defined within classes are considered inline.内联规则规定在类中定义的函数被认为是内联的。 And inline functions are permitted to have duplicate definitions provided (1) they live in separate translation units and (2) are identical.并且允许内联函数具有重复的定义,前提是 (1) 它们位于不同的翻译单元中并且 (2) 是相同的。 I'm paraphrasing, but that's the gist.我是转述,但这就是要点。

That explains why the functions are legal, but not why multiple definitions of the class or enum are ok.这解释了为什么函数是合法的,但不能解释为什么类或枚举的多个定义是可以的。 Likely a similar explanation I imagine, but it would be good to know for sure.我想象中可能有类似的解释,但最好能确定一下。

Generally when you compile a definition that is namespace scoped (like functions or global variables), your compiler will emit a global symbol for it.通常,当您编译命名空间范围内的定义(如函数或全局变量)时,您的编译器会为其发出一个全局符号。 If this appears in multiple translation units, there will be a conflict during link-time since there are multiple definitions (which happen to be equivalent, but the linker can't check this).如果这出现在多个翻译单元中,则会在链接时发生冲突,因为有多个定义(碰巧是等价的,但链接器无法检查)。

This is part of the one definition rule : Exactly one definition of a function or variable is allowed in the entire program, in one of the translation units.这是一个定义规则的一部分:在整个程序中,在一个翻译单元中,只允许定义一个函数或变量。

There are some exceptions to this, for example, class definitions and inline functions/variables.对此有一些例外,例如类定义和内联函数/变量。 However, definitions must be the exact same (textually) in all the translation units they appear in. Class definitions are meant to be #include d, so it makes sense to allow them to appear in multiple translation units.但是,定义在它们出现的所有翻译单元中必须完全相同(文本上)。类定义意味着#include d,因此允许它们出现在多个翻译单元中是有意义的。

If you define a member function inside the class body they are implicitly inline because otherwise you would not be able to include the class definition with the member function definition without breaking ODR.如果您在类体内定义成员函数,则它们是隐式inline ,否则您将无法在不破坏 ODR 的情况下将类定义包含在成员函数定义中。 For example, these three are functionally equivalent:例如,这三个在功能上是等效的:

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; }

You can do this to your namespace scoped functions/variables too: inline int test() { return 5; }您也可以对命名空间范围内的函数/变量执行此操作: inline int test() { return 5; } inline int test() { return 5; } and inline int x = 20; inline int test() { return 5; }inline int x = 20; would have compiled with no further issue.将编译没有进一步的问题。

This is implemented by the compiler emitting "specially marked" symbols for inline entities, and the linker picking one arbitrarily since they should all be the same.这是由编译器为内联实体发出“特殊标记”符号实现的,链接器任意选择一个,因为它们应该都是相同的。

The same exception to ODR also exists for templated functions / variables and enum declarations, since they are also meant to live in header files.模板化函数/变量和枚举声明也存在 ODR 的相同例外,因为它们也存在于头文件中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM