前向声明的目的是什么?

8

例如,在下面的示例中,如果在定义类之前声明类,将会获得什么额外的好处呢?

class test;

class test
{
  .....
};

2
下次您可能希望有一个真正标题的标题 :) - ereOn
4
将您的问题编辑为有意义的标题。 - Puppy
这似乎是 https://dev59.com/TXE85IYBdhLWcg3wvGOm 的重复,该问题因为重复而被关闭。 - Ferruccio
6个回答

25

C++(类似于C语言)的设计目标是可以被单遍编译器实现。在编译器需要在类实际定义之前知道符号引用一个类的情况下,需要使用前向引用。其中经典的例子是当两个类需要包含指向彼此的指针时。

class B;

class A {
  B* b;
};

class B {
  A* a;
};

如果没有对B的前向引用,编译器就无法成功解析A的定义,而将B的定义放在A之前也不能解决这个问题。

在像C#这样需要两遍编译的语言中,您不需要前向引用。

class A {
  B b;
}

class B {
  A a;
}

因为编译器的第一遍扫描会获取所有符号定义。当编译器进行第二遍扫描时,它可以说:“我知道B是一个类,因为我在第一遍扫描中看到了这个定义”。


@Lazer:我不清楚GCC的具体情况,但大多数现代C和C++编译器都使用多次遍历。它们只对实际源代码进行一次遍历,并将其转换为某种内部表示形式。然后,在生成代码的过程中,它们会对这种内部表示进行一次或多次优化遍历。 - Ferruccio
1
我认为多遍扫描是用于解析而不是优化的。 - Tobias Langner
2
对我来说,C和C++希望编译器在类范围内进行单遍通行的做法很奇怪,因为仅在汇编代码编译中解析标签就需要编译器进行两次通行。要求特定的声明顺序对程序员来说似乎过于繁琐。 - Gravity
@Ferruccio:“你不能通过将B的定义放在A之前来解决问题。”为什么会这样? - Mehdi Charife
1
@MehdiCharife - 这两个类相互引用。如果没有最初的前向声明,无论你以什么顺序放置它们,第一个类定义都将引用尚未定义的类。 - Ferruccio
@Ferruccio,我认为您的答案可以通过添加上述解释变得更加完整。 - Mehdi Charife

9
如果访问了类的成员/方法或需要知道其大小,则编译器需要该类的定义。在其他情况下,仅需使用前向声明即可。这可以节省编译时间。 例如:
class A { 
  B m_b; 
  C* m_ptrC;
}; 

对于这个类,你需要B的定义(需要大小),以及C的声明(指针有固定的大小)。只需要包含B的头文件,而不需要C的头文件。对于C,前向声明就足够了。

a.h:

#ifndef A_H
#define A_H

#include <b.h>
class C;

class A
{
  B m_b;
  C* m_ptrC;
}
#endif

使用c的前向声明(而不是包含c.h,这也是可能的)可以避免在包含a.h时每次都解析c.h,对于大型项目,这可能会节省很多编译时间。另一个好处是,在这种情况下,c.h的更改不会触发a的重新编译。我不知道编译器是否会识别这一点,如果你包含c.h而不是前向声明它。
要了解更多信息,请尝试理解pimpl习惯用法(只需搜索即可,你会得到很多结果)。
当然,在a.cpp中,如果你实际上对指向c的指针做了些什么(例如m_ptrC->Add()),你需要包含c.h。但是,与在头文件中读取n次相比,在大型项目中经常使用的类的a.cpp只读取一次。
前向声明还允许循环依赖关系。 例子:
class B;

class A {
 B* m_ptrB;
}

class B {
 A* m_ptrA;
}

请记住 - 如果您使用前向声明,就不能使用任何有关大小和方法的信息。如果两个类相互包含(其中一个类不需要前向引用),也是如此。我个人认为循环引用是不好的风格,如果可能的话应该避免使用。

有关更多信息:C++ FAQ

感谢您提供有关循环依赖的评论,我简单地忘记了它们。


编译器效率使用是合法的,但我认为循环引用的使用更适合作为考虑前向引用目的的起点。 - Brian

4

首先,类被声明,然后被定义。

声明:它只是告诉编译器:好的,这里有个东西(方法、类等),可以用给定的名称。它只将给定的名称与某些东西“绑定”起来。

定义:它告诉编译器:好的,这里是方法、类等实际上如何执行它们的任务的内容和方式。如果定义了某些东西,则编译器会为其实际分配空间。

你可以在这里查看。


1

第一行是所谓的前向声明。它将类的名称带入当前命名空间,但并不实际定义它。


0
嗯,这个问题不是很清楚。 然而,提供的代码声明了一个名为test的类,并在下面定义它,省略了实际成员(...)。

0
简单来说,使用第一种形式时,您可以在不需要完整实现的情况下持有指向类的指针或引用。当两个类紧密交互且它们的实现或定义难以分离时,这非常有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接