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

问题描述 投票:0回答:3

这个问题建立在这两个 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
c++ namespaces g++
3个回答
2
投票

阅读完cppreference:内联说明符后,我有了部分答案。内联规则规定类内定义的函数被视为内联。内联函数允许有重复的定义,前提是 (1) 它们位于不同的翻译单元中并且 (2) 相同。我是在转述,但这就是要点。

这解释了为什么函数是合法的,但不能解释为什么类或枚举的多个定义是可以的。我想象可能有类似的解释,但最好能确定一下。


1
投票

通常,当您编译名称空间范围内的定义(如函数或全局变量)时,编译器将为它发出一个全局符号。如果这出现在多个翻译单元中,则在链接时会出现冲突,因为有多个定义(恰好是等效的,但链接器无法检查这一点)。

这是一个定义规则的一部分:在整个程序中,在一个翻译单元中,只允许对函数或变量进行一个定义。

有一些例外,例如类定义和内联函数/变量。但是,定义在它们出现的所有翻译单元中必须完全相同(文本上)。类定义意味着

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


0
投票

我的解释

为什么类/结构/枚举没有出现多重定义错误?为什么它只适用于函数或变量?

也就是说,函数和变量都有分配给它们的特定内存区域,链接器通过它们的符号名称引用该内存区域。例如,函数是 .text 区域中的一段代码。或者一个变量可能存在于.bss等中。因此,它们不应有多个定义。但类/结构/枚举定义只是一个布局、一个计划、一个内存块组织的定义。

© www.soinside.com 2019 - 2024. All rights reserved.