什么是 C++ 中的前向声明?

Posted

技术标签:

【中文标题】什么是 C++ 中的前向声明?【英文标题】:What are forward declarations in C++? 【发布时间】:2011-06-13 01:19:49 【问题描述】:

在:http://www.learncpp.com/cpp-tutorial/19-header-files/

以下提到:

添加.cpp:

int add(int x, int y)

    return x + y;

main.cpp:

#include <iostream>

int add(int x, int y); // forward declaration using function prototype

int main()

    using namespace std;
    cout << "The sum of 3 and 4 is " << add(3, 4) << endl;
    return 0;

我们使用了前向声明,以便编译器在编译 main.cpp 时知道“add”是什么。如前所述,为要使用的每个位于另一个文件中的函数编写前向声明会很快变得乏味。

您能进一步解释“前向声明”吗?如果我们在main()函数中使用它会出现什么问题?

【问题讨论】:

“前向声明”实际上只是一个声明。请参阅(结尾)此答案:***.com/questions/1410563/… 【参考方案1】:

为什么在 C++ 中需要前向声明

编译器希望确保您没有犯拼写错误或将错误数量的参数传递给函数。因此,它坚持在使用它之前首先看到“add”(或任何其他类型、类或函数)的声明。

这实际上只是允许编译器更好地验证代码并允许它整理松散的末端,以便它可以生成一个看起来整洁的目标文件。如果您不必转发声明内容,编译器将生成一个目标文件,该文件必须包含有关函数add 可能是什么的所有可能猜测的信息。并且链接器必须包含非常聪明的逻辑来尝试找出您实际打算调用的add,当add 函数可能存在于不同的目标文件中时,链接器将与使用 add 生成的目标文件连接dllexe。链接器可能会得到错误的add。假设您想使用int add(int a, float b),但不小心忘记了写它,但链接器发现了一个已经存在的int add(int a, int b),并认为这是正确的并使用了它。您的代码可以编译,但不会按照您的预期进行。

因此,为了保持明确并避免猜测等,编译器坚持在使用之前声明所有内容。

声明与定义的区别

顺便说一句,了解声明和定义之间的区别很重要。声明只提供了足够的代码来显示某些东西的样子,因此对于函数,这是返回类型、调用约定、方法名称、参数及其类型。但是,不需要该方法的代码。对于定义,您需要声明,然后还需要函数的代码。

前向声明如何显着减少构建时间

您可以通过#includ'ing 已包含函数声明的标头将函数声明放入当前的.cpp.h 文件中。但是,这可能会减慢您的编译速度,尤其是如果您将标头 #include 放入程序的 .h 而不是 .cpp 时,因为所有 #includes .h 您正在编写的内容最终都会 #include'也为您编写#includes 的所有标题添加。突然间,编译器有#included 页面和需要编译的代码页面,即使您只想使用一两个函数也是如此。为避免这种情况,您可以使用前向声明,只需在文件顶部自己键入函数的声明。如果您只使用几个函数,与总是#include 标头相比,这确实可以使您的编译更快。对于非常大的项目,差异可能是一个小时或更长时间的编译时间缩短到几分钟。

打破两个定义相互使用的循环引用

此外,前向声明可以帮助您打破循环。这是两个函数都试图相互使用的地方。当这种情况发生时(这是一件非常有效的事情),您可以#include 一个头文件,但该头文件会尝试#include 您当前正在编写的头文件...然后#includes 另一个标头,其中 #include 包含您正在编写的标头。您陷入了鸡与蛋的局面,每个头文件都试图重新#include 另一个。要解决此问题,您可以在其中一个文件中前向声明您需要的部分,并将 #include 保留在该文件之外。

例如:

文件 Car.h

#include "Wheel.h"  // Include Wheel's definition so it can be used in Car.
#include <vector>

class Car

    std::vector<Wheel> wheels;
;

文件 Wheel.h

嗯...这里需要声明Car,因为Wheel 有一个指向Car 的指针,但这里不能包含Car.h,因为它会导致编译器错误。如果包含Car.h,则将尝试包含Wheel.h,其中将包含Car.h,其中将包含Wheel.h,这将永远持续下去,因此编译器会引发错误。解决方案是转发声明Car

class Car;     // forward declaration

class Wheel

    Car* car;
;

如果Wheel 类有方法需要调用Car 的方法,这些方法可以在Wheel.cpp 中定义,Wheel.cpp 现在可以包含Car.h 而不会导致循环。

【讨论】:

当一个函数对两个或多个类友好时,前向声明也是必要的 嗨 Scott,关于构建时间的观点:您认为在 .cpp 文件中始终根据需要转发声明和包含标头是一种常见/最佳做法吗?从阅读您的答案看来应该是这样,但我想知道是否有任何警告? @Zepee 这是一个平衡点。对于快速构建,我会说这是一种很好的做法,我建议尝试一下。但是,如果类型名称等仍在更改,则可能需要一些努力和额外的代码行可能需要维护和更新(尽管工具在自动重命名方面变得更好)。所以有一个权衡。我见过没有人打扰的代码库。如果您发现自己重复相同的前向定义,您总是可以将它们放入一个单独的头文件中并包含它,例如:***.com/questions/4300696/what-is-the-iosfwd-header 头文件相互引用时需要前向声明:即***.com/questions/396084/… 我可以看到这让我团队中的其他开发人员成为代码库的真正坏公民。如果您不需要前向声明的评论,例如// From Car.h,那么您可以创造一些毛茸茸的情况,试图在路上找到一个定义,保证。【参考方案2】:

编译器会查找当前翻译单元中使用的每个符号是否先前在当前单元中声明过。在源文件的开头提供所有方法签名,而稍后提供定义,这只是风格问题。它的重要用途是当您使用指向类的指针作为另一个类的成员变量时。

//foo.h
class bar;    // This is useful
class foo

    bar* obj; // Pointer or even a reference.
;

// foo.cpp
#include "bar.h"
#include "foo.h"

因此,尽可能在类中使用前向声明。如果您的程序只有函数(带有 ho 头文件),那么在开始时提供原型只是风格问题。如果头文件存在于一个只有函数的头文件的普通程序中,无论如何都会出现这种情况。

【讨论】:

【参考方案3】:

因为 C++ 是自上而下解析的,所以编译器在使用它们之前需要了解它们。所以,当你参考:

int add( int x, int y )

在主函数中编译器需要知道它的存在。为了证明这一点,试着把它移到 main 函数下面,你会得到一个编译器错误。

因此,“转发声明”就是它在锡罐上所说的内容。它在使用之前声明了一些东西。

通常您会在头文件中包含前向声明,然后以与包含 iostream 相同的方式包含该头文件。

【讨论】:

【参考方案4】:

C++ 中的术语“前向声明”主要仅用于类声明。请参阅(结尾)this answer,了解为什么类的“前向声明”实际上只是一个简单的类声明,并带有一个花哨的名称。

换句话说,“转发”只是为术语添加了镇流器,因为任何声明都可以被视为转发,因为它声明了一些标识符之前它被使用。

(关于什么是声明而不是定义,再次参见What is the difference between a definition and a declaration?)

【讨论】:

【参考方案5】:

当编译器看到add(3, 4) 时,它需要知道这意味着什么。通过前向声明,您基本上可以告诉编译器add 是一个接受两个整数并返回一个整数的函数。这对编译器来说很重要,因为它需要将 4 和 5 以正确的表示形式放入堆栈,并且需要知道 add 返回的东西是什么类型。

那时,编译器并不担心add实际实现,即它在哪里(或者是否有甚至一个)以及是否编译。稍后会看到这一点,调用链接器时编译源文件之后。

【讨论】:

【参考方案6】:
int add(int x, int y); // forward declaration using function prototype

你能解释一下“前向声明”吗 更进一步?如果有什么问题 我们在 main() 函数中使用它?

#include"add.h" 相同。如果您知道,预处理器会在您编写#include 指令的.cpp 文件中扩展您在#include 中提到的文件。也就是说,如果你写#include"add.h",你会得到同样的东西,就像你在做“前向声明”。

我假设add.h 有这一行:

int add(int x, int y); 

【讨论】:

【参考方案7】:

关于以下内容的快速附录:通常您将这些前向引用放入属于 .c(pp) 文件的头文件中,其中实现了函数/变量等。在您的示例中,它看起来像这样: 添加.h:

extern int add(int a, int b);

关键字 extern 表明该函数实际上是在外部文件中声明的(也可以是库等)。 你的 main.c 看起来像这样:

#包括 #include "add.h" 主函数() . . .

【讨论】:

但是,我们不是只把声明放在头文件里吗?我认为这就是为什么函数在“add.cpp”中定义,因此使用前向声明?谢谢。【参考方案8】:

一个问题是,编译器不知道你的函数传递了哪种值;假设函数在这种情况下返回int,但这可能是正确的,也可能是错误的。另一个问题是,编译器不知道您的函数需要哪种类型的参数,并且如果您传递的值类型错误,则无法警告您。有一些特殊的“提升”规则适用于将浮点值传递给未声明的函数(编译器必须将它们扩展为 double 类型),这通常不是函数实际期望的,导致很难找到错误在运行时。

【讨论】:

以上是关于什么是 C++ 中的前向声明?的主要内容,如果未能解决你的问题,请参考以下文章

在另一个文件c ++中的前向声明

C++ 中 typedef 的前向声明

C++ 中嵌套类型/类的前向声明

C++ 类的前向声明的用法

类成员的前向类声明

不允许嵌套类的前向声明的原因?