将头文件包含到实现文件中实际上有什么作用?

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

我正在阅读 Bjarne Stroustrups 使用 C++ 的原理和实践,并在第 8.3 章中遇到有关头文件的以下内容:

为了简化一致性检查,我们在源代码中 #include 一个标头 使用其声明的文件以及提供的源文件 这些声明的定义。这样,编译器就会捕获 尽快出错。

由此产生了两个问题:

  1. 将头文件包含到实现文件中实际上如何让编译器更快地捕获错误。
  2. 思考问题1。我开始想知道除了编译器能够更快地捕获错误之外,为什么还需要将头文件包含到实现文件中。我明白为什么将头文件导入到另一个翻译单元以访问其声明,然后让链接器将这些声明连接到实现是合乎逻辑的。但是链接器如何在实现和声明之间建立联系呢?将头文件包含到实现文件中是否告诉链接器声明 x 应该连接到定义 x?

然后我进行了一些测试,看看如果我不将标头包含到实现文件中会发生什么:

main.cpp:

#include "test_utility.h"

int main()
{
    utility::printString("hello");
}

test_utility.h:

#ifndef TEST_UTILITY
#define TEST_UTILITY

#include <string>

namespace utility
{
    void printString(const std::string& str);
}

#endif

test_utility.cpp:

#include <iostream>
#include <string>
//#include "test_utility.h"

namespace utility
{
    void printString(const std::string& str)
    {
        std::cout << str << std::endl;
    }
}

我预计这段代码会出现链接器错误,指出找不到

printString()
的定义。令我惊讶的是,编译器(gcc Ubuntu 11.4.0-1ubuntu1~22.04)没有给出任何警告、错误,并且代码运行良好。那么将标头包含到实现文件中到底有什么意义呢?难道只是像 Stroustrup 所说的那样帮助编译器更快地捕获错误吗?看来链接器能够将相应的实现连接到相应的定义,而无需实现文件中的标头。

例如:

#include <iostream>
#include <string>
//#include "test_utility.h"

namespace utility
{
    void printString(const std::string& str, int x)
    {
        std::cout << str << std::endl;
    }
}

在我的测试中仍然生成链接器错误。

c++ linker header
2个回答
0
投票

头文件基本上说“存在一个函数

void printString(const std::string&, int)
,但没有给出实际的实现。它仍然足以让编译器编译调用该函数的代码。

实现文件包含标头,以确保它是同一件事,并获取在那里声明的其他内容。

如果您创建一个全局函数而没有前面的原型声明,编译器甚至可以警告您,这样您就不会意外错过标头。对于

g++
,这是由
-Wmissing-declarations
启用的(这是我一直启用的众多警告之一)。由于您没有收到该警告,因此您的第二个示例编译得很好,只有链接器抱怨,因为您承诺存在的函数实际上并不存在于您的代码中。

另外,对于类来说,头文件已经提供了很多详细信息,所以它是绝对必需的。对于函数,您可以不包含标头,但根本不建议这样做,因为您不想在代码中不必要地重复某些内容,并且您希望编译器尽早检查而不是稍后检查。
此外,大多数标头不只是声明一个函数——还有类和类型。


0
投票

我想到并证明其合理性的一个例子

将头文件包含到实现文件中实际上如何让编译器更快地捕获错误。

考虑

header.h

int printString(const std::string& str);

和实施

impl.cpp

int printString(const std::string& str) {
    std::cout << str << std::endl;
    return 0;
}

然后假设您在代码中的某个位置包含

header.h
并使用
int res = printString("hello");
。您的
res
等于 0。然后您决定进行重构,并将实现更改为

void printString(const std::string& str) {
    std::cout << str << std::endl;
}

现在您的程序可以编译并链接,但是此语句

int res = printString("hello");
具有未定义的行为。如果您在实现中包含
header.h
,编译器会抱怨

错误:对‘void printString(const string&)’的新声明产生歧义

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