C++ 中的前向声明是什么?
在此链接中,提到了以下内容:
add.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
函数中使用它会有什么问题呢?
At this link, the following was mentioned:
add.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;
}
We used a forward declaration so that the compiler would know what "
add
" was when compilingmain.cpp
. As previously mentioned, writing forward declarations for every function you want to use that lives in another file can get tedious quickly.
Can you explain "forward declaration" further? What is the problem if we use it in the main
function?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
为什么在 C++ 中需要前向声明
编译器希望确保您没有犯拼写错误或向函数传递了错误数量的参数。因此,它坚持在使用之前首先看到“add”(或任何其他类型、类或函数)的声明。
这实际上只是允许编译器更好地验证代码,并允许它清理零散的内容,以便它可以生成一个外观整洁的目标文件。如果您不必转发声明内容,编译器将生成一个目标文件,该文件必须包含有关函数
add
可能是什么的所有可能猜测的信息。当add
函数可能位于链接器的不同目标文件中时,链接器必须包含非常聪明的逻辑来尝试确定您实际打算调用哪个add
与使用 add 生成dll
或exe
的一个连接。链接器可能会得到错误的add
。假设您想使用int add(int a, float b)
,但不小心忘记写它,但链接器发现已经存在int add(int a, int b)
code> 并认为这是正确的,并使用了它。您的代码可以编译,但不会执行您期望的操作。因此,为了保持明确并避免猜测等,编译器坚持要求您在使用之前声明所有内容。
声明和定义之间的区别
顺便说一句,了解声明和定义之间的区别很重要。声明只是提供足够的代码来显示某些内容,因此对于函数来说,这是返回类型、调用约定、方法名称、参数及其类型。但是,该方法的代码不是必需的。对于定义,您需要声明,然后还需要函数的代码。
前向声明如何显着减少构建时间
您可以通过 #includ 将函数声明放入当前的
.cpp
或.h
文件中'ing 已包含函数声明的标头。但是,这可能会减慢您的编译速度,特别是如果您将标头#include
放入程序的.h
而不是.cpp
中,如下所示#includes 您正在编写的.h
的所有内容最终也会 #include' 您为其编写的 #includes 的所有标头。突然间,即使您只想使用一两个函数,编译器也会有 #included 页面和需要编译的代码页面。为了避免这种情况,您可以使用前向声明,只需在文件顶部自行键入函数的声明即可。如果您只使用几个函数,那么与始终 #include 标头相比,这确实可以使您的编译速度更快。对于真正的大型项目,差异可能是一个小时或更长的编译时间,缩短到几分钟。打破两个定义相互使用的循环引用
此外,前向声明可以帮助您打破循环。这是两个函数都尝试互相使用的地方。发生这种情况时(这是一件完全有效的事情),您可以
#include
一个头文件,但该头文件尝试#include
您的头文件当前正在编写...然后 #includes 另一个标头,其中 #includes 您正在编写的标头。您陷入了先有鸡还是先有蛋的情况,每个头文件都试图重新 #include 另一个头文件。要解决此问题,您可以在其中一个文件中前向声明所需的部分,并将 #include 保留在该文件之外。例如:
File Car.h
File Wheel.h
嗯...此处需要将
Car
声明为Wheel
有一个指向Car
的指针,但是Car.h
不能包含在此处,因为这会导致编译器错误。如果包含Car.h
,则会尝试包含Wheel.h
,其中包含Car.h
,其中包含Wheel .h
并且这种情况将永远持续下去,因此编译器会引发错误。解决方案是转发声明Car
:如果类
Wheel
有需要调用Car
方法的方法,这些方法可以定义在< code>Wheel.cpp 和Wheel.cpp
现在能够包含Car.h
而不会导致循环。Why forward-declare is necessary in C++
The compiler wants to ensure you haven't made spelling mistakes or passed the wrong number of arguments to the function. So, it insists that it first sees a declaration of 'add' (or any other types, classes, or functions) before it is used.
This really just allows the compiler to do a better job of validating the code and allows it to tidy up loose ends so it can produce a neat-looking object file. If you didn't have to forward declare things, the compiler would produce an object file that would have to contain information about all the possible guesses as to what the function
add
might be. And the linker would have to contain very clever logic to try and work out whichadd
you actually intended to call, when theadd
function may live in a different object file the linker is joining with the one that uses add to produce adll
orexe
. It's possible that the linker may get the wrongadd
. Say you wanted to useint add(int a, float b)
, but accidentally forgot to write it, but the linker found an already existingint add(int a, int b)
and thought that was the right one and used that instead. Your code would compile, but wouldn't be doing what you expected.So, just to keep things explicit and avoid guessing, etc, the compiler insists you declare everything before it is used.
Difference between declaration and definition
As an aside, it's important to know the difference between a declaration and a definition. A declaration just gives enough code to show what something looks like, so for a function, this is the return type, calling convention, method name, arguments, and their types. However, the code for the method isn't required. For a definition, you need the declaration and then also the code for the function too.
How forward-declarations can significantly reduce build times
You can get the declaration of a function into your current
.cpp
or.h
file by #includ'ing the header that already contains a declaration of the function. However, this can slow down your compile, especially if you#include
a header into a.h
instead of.cpp
of your program, as everything that #includes the.h
you're writing would end up #include'ing all the headers you wrote #includes for too. Suddenly, the compiler has #included pages and pages of code that it needs to compile even when you only wanted to use one or two functions. To avoid this, you can use a forward-declaration and just type the declaration of the function yourself at the top of the file. If you're only using a few functions, this can really make your compiles quicker compared to always #including the header. For really large projects, the difference could be an hour or more of compile time bought down to a few minutes.Break cyclic references where two definitions both use each other
Additionally, forward-declarations can help you break cycles. This is where two functions both try to use each other. When this happens (and it is a perfectly valid thing to do), you may
#include
one header file, but that header file tries to#include
the header file you're currently writing... which then #includes the other header, which #includes the one you're writing. You're stuck in a chicken and egg situation with each header file trying to re #include the other. To solve this, you can forward-declare the parts you need in one of the files and leave the #include out of that file.Eg:
File Car.h
File Wheel.h
Hmm... the declaration of
Car
is required here asWheel
has a pointer to aCar
, butCar.h
can't be included here as it would result in a compiler error. IfCar.h
was included, that would then try to includeWheel.h
which would includeCar.h
which would includeWheel.h
and this would go on forever, so instead the compiler raises an error. The solution is to forward declareCar
instead:If class
Wheel
had methods which need to call methods ofCar
, those methods could be defined inWheel.cpp
andWheel.cpp
is now able to includeCar.h
without causing a cycle.编译器查找当前翻译单元中使用的每个符号是否先前在当前单元中声明过。这只是一个风格问题,在源文件的开头提供所有方法签名,而定义则在稍后提供。它的重要用途是当您使用指向一个类的指针作为另一个类的成员变量时。
因此,尽可能在类中使用前向声明。如果你的程序只有函数(带有ho头文件),那么在开始时提供原型只是一个风格问题。如果头文件存在于一个只有函数的标头的普通程序中,无论如何都会出现这种情况。
The compiler looks for each symbol being used in the current translation unit is previously declared or not in the current unit. It is just a matter of style providing all method signatures at the beginning of a source file while definitions are provided later. The significant use of it is when you use a pointer to a class as member variable of another class.
So, use forward-declarations in classes when ever possible. If your program just has functions( with ho header files), then providing prototypes at the beginning is just a matter of style. This would be anyhow the case had if the header file was present in a normal program with header that has only functions.
因为 C++ 是自上而下解析的,所以编译器需要在使用事物之前了解它们。因此,当您在主函数中引用:时,
编译器需要知道它存在。为了证明这一点,请尝试将其移至主函数下方,您将收到编译器错误。
因此,“转发声明”就是其包装上的内容。它在使用之前声明一些东西。
通常,您会在头文件中包含前向声明,然后以与包含 iostream 相同的方式包含该头文件。
Because C++ is parsed from the top down, the compiler needs to know about things before they are used. So, when you reference:
in the main function the compiler needs to know it exists. To prove this try moving it to below the main function and you'll get a compiler error.
So a 'Forward Declaration' is just what it says on the tin. It's declaring something in advance of its use.
Generally you would include forward declarations in a header file and then include that header file in the same way that iostream is included.
C++ 中的术语“前向声明”主要仅用于类声明。请参阅(结尾)这个答案 为什么类的“前向声明”实际上只是一个带有奇特名称的简单类声明。
换句话说,“前向”只是向该术语添加了镇流器,因为任何声明都可以被视为前向,因为它在使用之前声明了一些标识符。
(至于什么是声明而不是定义,请再次参见定义和声明之间有什么区别? )
The term "forward declaration" in C++ is mostly only used for class declarations. See (the end of) this answer for why a "forward declaration" of a class really is just a simple class declaration with a fancy name.
In other words, the "forward" just adds ballast to the term, as any declaration can be seen as being forward in so far as it declares some identifier before it is used.
(As to what is a declaration as opposed to a definition, again see What is the difference between a definition and a declaration?)
当编译器看到
add(3, 4)
时,它需要知道这意味着什么。通过前向声明,您基本上告诉编译器add
是一个接受两个 int 并返回一个 int 的函数。这对于编译器来说是重要的信息,因为它需要将 4 和 5 以正确的表示形式放入堆栈,并且需要知道 add 返回的内容是什么类型。那时,编译器并不担心
add
的实际实现,即它在哪里(或者是否有有),并且如果它编译。稍后,在调用链接器时编译源文件之后,就会看到这一点。When the compiler sees
add(3, 4)
it needs to know what that means. With the forward declaration you basically tell the compiler thatadd
is a function that takes two ints and returns an int. This is important information for the compiler becaus it needs to put 4 and 5 in the correct representation onto the stack and needs to know what type the thing returned by add is.At that time, the compiler is not worried about the actual implementation of
add
, ie where it is (or if there is even one) and if it compiles. That comes into view later, after compiling the source files when the linker is invoked.它与
#include"add.h"
相同。如果您知道,预处理器会在您编写#include
指令的 .cpp 文件中扩展您在#include
中提到的文件。这意味着,如果您编写#include"add.h"
,您会得到相同的结果,就像您在执行“前向声明”一样。我假设
add.h
有这一行:It's same as
#include"add.h"
. If you know,preprocessor expands the file which you mention in#include
, in the .cpp file where you write the#include
directive. That means, if you write#include"add.h"
, you get the same thing, it is as if you doing "forward declaration".I'm assuming that
add.h
has this line:一个关于以下内容的快速附录:通常您将这些前向引用放入属于实现函数/变量等的 .c(pp) 文件的头文件中。在你的例子中它看起来像这样:
add.h:
关键字 extern 表明该函数实际上是在外部文件中声明的(也可以是库等)。
你的 main.c 看起来像这样:
one quick addendum regarding: usually you put those forward references into a header file belonging to the .c(pp) file where the function/variable etc. is implemented. in your example it would look like this:
add.h:
the keyword extern states that the function is actually declared in an external file (could also be a library etc.).
your main.c would look like this:
一个问题是,编译器不知道你的函数传递的是哪一种值;假设该函数在这种情况下返回一个
int
,但这可能是正确的,也可能是错误的。另一个问题是,编译器不知道您的函数需要哪种类型的参数,并且如果您传递了错误类型的值,则无法警告您。有一些特殊的“提升”规则,在传递浮点值到未声明的函数时适用(编译器必须将它们扩展为 double 类型),这通常不是函数实际期望的,导致很难发现错误在运行时。One problem is, that the compiler does not know, which kind of value is delivered by your function; is assumes, that the function returns an
int
in this case, but this can be as correct as it can be wrong. Another problem is, that the compiler does not know, which kind of arguments your function expects, and cannot warn you, if you are passing values of the wrong kind. There are special "promotion" rules, which apply when passing, say floating point values to an undeclared function (the compiler has to widen them to type double), which is often not, what the function actually expects, leading to hard to find bugs at run-time.