多重定义错误 #include -ing .h header

Multiple definition error #include -ing .h header

[环境为QTCreator 4.2.1,编译器为MinGW 5.8]

我创建了一个 header “header.h” 使用以下代码:

#ifndef HEADER_H
#define HEADER_H

int variable = 0;

#endif // HEADER_H

我试图将 header 包含在两个 .cpp 文件中,出现多个定义错误。

但是我像这样更改了代码并在两个 .cpp 文件之一中提到了静态变量 (int Class::variable;):

#ifndef HEADER_H
#define HEADER_H

class Class{
public:
    static int variable;
};

#endif // HEADER_H

错误消失了。但是为什么我不能在没有错误的情况下两次包含 header 并在两个 .cpp 文件中使用全局变量(并直接使用它,而不是作为静态 class 成员)?

结合起来可以解释这三个主要原因:

  1. 它们是两个完全不同的“东西”。语法可能看起来很相似,但它们在 C++ 中做了两件完全不同的事情。
  2. The One Definition Rule
  3. #include 头文件在逻辑上是等价的,并且完全等同于将头文件的内容物理插入任何 #included 它。

因此,在第一种情况下,对于包含 int variable = 0; 的头文件,#include 这个文件的每个翻译单元都变得完全一样

int variable = 0;

#include 的每个 .cpp 文件中逐字显示。完全相同的。而且,很明显,这违反了单一定义规则(如果不止一个 .cpp 有这个,并且它们链接在一起)。这实际上 创建了 一个 int 变量。它定义了它。一个名为 variable 的对象凭空出现。每个 .cpp 包含此文件的文件都成为 int variable 的骄傲拥有者。它存在于每个 .cpp 个文件中,这违反了一个定义规则。

另一方面,您有一个 class 声明,此处:

class Class{
public:
    static int variable;
};

这不定义任何东西。它只是声明一个 Class,它有一个 static class 成员。而已。它不定义任何对象。它不会突然出现任何东西,也不会凭空物化任何东西。这只是声明了一个class,以及里面的内容。由于此处未定义任何内容的基本原因,单一定义规则没有发挥作用。

因此,尽管语法非常相似,但在 C++ 中看起来非常相似的代码的最终结果却大不相同。

在经历了同样的问题并做了一些研究之后,我想补充上面的答案!共享变量有多种方式,根据您的情况,您需要使用不同的选项。

全局变量声明

变量前的关键字 extern 允许您声明一个完全全局的变量,之后可以在您的所有代码中使用(只要您在该变量所在的位置包含特定的 header声明)。这样做的优点是设置起来非常简单,但缺点是随着您的代码变得越来越广泛,跟踪这些全局变量的含义变得更加困难。因此,一般情况下,不建议使用全局变量。

例如:

Header 文件 - header

#ifndef HEADER_H
#define HEADER_H
extern int global_x;

void print_global_x();

#endif

来源 1 - 主要

#include "header.h"

// since global_x still needs to be defined somewhere,
// we define it (for example) in this source file
int global_x;

int main()
{
    //set global_x here:
    global_x = 5;

    print_global_x();
}

来源 2 - header.cpp

#include <iostream>
#include "header.h"

void print_global_x()
{
    //print global_x here:
    std::cout << global_x << std::endl;
}

(示例取自 this answer

使用命名空间

在某些情况下使用全局变量可能不清楚。如果您在许多文件中使用它,您可能会忘记该变量的来源以及它的确切编辑位置。最重要的是,您可能有许多全局变量和方法,每个都特定于代码的各个方面。命名空间允许您指定每个变量的相关内容。例如:

Header 文件

#ifndef HEADER_H
#define HEADER_H
#include <iostream>
namespace x_storage
{
    int x;
    void print_x()
    {
        std::cout << x << std::endl;
    }
}

namespace complicated_algorithms
{
    int x;
    void add_10_to_x_and_print()
    {
        x = x + 10;
        std::cout << x << std::endl;
    }
}
#endif

来源 1 - 主要

#include "header.h"
int main()
{
    x_storage::x = 3; //set x in the x_storage namespace to 3
    complicated_algorithms::x = 3; // set x in complicated_algorithms namespace to 3
    int x = 4; //set x in the global namespace to 4

    x_storage::print_x(); // prints 3
    complicated_algorithms::add_10_to_x_and_print(); // prints 13
    x_storage::print_x(); // prints 3
    std::cout << x << std::endl; //prints 4
}

Class 带有静态元素

最后,您可以拥有一个所有元素都是静态的 class。当您希望这些变量在所有代码中共享但这些变量需要不断修改时,这就派上用场了。这样,所有修改都发生在 class 内。我发现这个有用的地方是在我的测试环境中,因为我有一些变量在整个测试过程中重复使用,但我想在每次测试开始时重置它们的值。示例:

Header 文件 - Counter.h

#ifndef COUNTER_H
#define COUNTER_H
class Counter
{
public:
    static int get_counter();
    static void add_one();
    static void reset();

private: 
    static int counter;

}
#endif

来源 1 - Counter.cc

int Counter::counter = 0; //must always declare even if not set to anything

int Counter::get_counter()
{
    return counter;
}

void Counter::add_one()
{
    counter++;
}
void Counter::reset()
{
    counter = 0;
}

来源 2 - 使用计数器

#include "Counter.h"


void method1()
{
    Counter::reset();
    if(first_condition_met()) Counter::add_one();
}

void method2()
{
    if(another_condition_met()) Counter::add_one();
    else Counter::reset();
}

这是一个简单的代码,您可以在其中尝试满足条件,以了解计数器在给定不同真值表时的行为。在其他更复杂的情况下,类似的方法可能非常有用。