前置声明和包含的区别

29

考虑以下两种情况(编辑只是为了完整问题并使其更清晰)

情况1:(如下所述,无法编译)

//B.h
#ifndef B_H
#define B_H
#include "B.h"

class A;

class B { 
        A obj;
        public:
        void printA_thruB();

         };  
#endif

//B.cpp
#include "B.h"
#include <iostream>

void B::printA_thruB(){
        obj.printA();
        }   


//A.h;
#ifndef A_H
#define A_H

#include "A.h"

class A { 
        int a;
        public:
        A();
        void printA();

         };  
#endif   

//A.cpp                           
#include "A.h"                    
#include <iostream>               

A::A(){                           
        a=10;                     
        }                         

void A::printA()                  
{                                 
std::cout<<"A:"<<a<<std::endl;    
}  


//main.cpp
 #include "B.h"
  #include<iostream>
 using namespace std;

 int main()
 {
 B obj;
 obj.printA_thruB();
 }

第二种情况:(唯一的修改是...可以在不编译错误的情况下正常工作)

//B.h

#include "A.h" //Add this line
//class A;     //comment out this line

假设A.cpp和B.cpp一起编译。上述两种情况是否有任何区别?有没有理由更喜欢其中一种方法?

编辑: 那么,如何使场景1起作用。


1
你已经将A.cpp和B.cpp编译在一起了,但是A.h和B.h文件怎么处理了呢? - Dennis Zickefoose
@Dennis,我为你澄清了问题。 - Sii
6个回答

20

前向声明不是头文件包含的替代品。

正如其名称所示,前向声明仅是一个声明,而不是定义。

因此,您将声明编译器它是一个类,并且我在这里仅声明并将在使用时提供定义。所以,通常您会在头文件中进行前向声明,并在使用前向声明类成员的.cpp文件中#include

通过这样做,您所做的就是,在包含头文件的任何地方,都只会有一个类的声明,而不是整个内容#included...

但是话虽如此,当编译器需要类的定义时,它应该被#include

因此,在您的情况下,A obj;需要class A的定义,因此您应该#include...

我自己也问过类似的问题here和另外一个类似的问题,那里也有一个很好的答案......

希望对您有所帮助..


在包含头文件时,我们做的是相同的事情...只是声明罢了...定义应该放在.cpp源文件中。所以,如果我将我的 A obj 改为指针 A *obj ,它应该可以工作吧。(但实际上并没有) - Sii
谢谢你的帮助..我想我已经让自己困惑了一整天。 - Sii

14

当你编译B.cpp文件时,如果使用Case 1,会产生一个“不完整类型”错误。这是因为类B包含一个类A对象,需要在类B定义之前提供类A的定义(特别是大小)。

或者,你可以选择将some_variable变成指向或引用类A的指针,在这种情况下,在B.h中进行的前向声明就足够了。你仍然需要在B.cpp中提供A的完整定义(假设你实际使用了A的成员函数/数据)。


3
澄清一下,情况1只有在你试图调用A的任何方法时才会产生编译错误。在这种情况下,应该会出错,因为你将隐式调用构造函数。但在另一种情况下,如果B的某个方法仅仅接收一个指向A对象的指针,然后将其传递给超出B责任范围的某些其他方法/函数,那么编译器不知道A的定义也没有问题。 - Hitesh
你说得对,Drew。我确实遇到了编译错误。那么我该如何解决呢?我想可以使用前向声明来代替头文件包含。 - Sii
@Hitesh:不会。Case 1总是会产生编译错误,因为编译器不知道A的大小,因此它无法知道如何布置B对象。 - Drew Hall
@MrProg:在定义类B之前,您需要完全定义A(包括A.h),或者您需要将A的指针或引用包含在B中。在这种情况下,简单/明显的解决方案是在B.h中包含A.h。为什么您似乎不愿意这样做呢? - Drew Hall
不是。我正在努力理解前向声明,假设它可以使类在不包含头文件的情况下可访问...类似于extern。 - Sii
显示剩余2条评论

4

在类彼此引用的情况下,需要使用前向申明。

//A.h

class B;

class A {
    B* someVar;
}

//B.h
#include <A.h>

class B {
    A* someVar;
}

但在你所描述的情况下,这样做没有任何好处。


谢谢提供的信息。如果在我的情况下B正在访问A的某个变量,会怎样呢? - Sii
在您的情况下,您确实希望在B.h内显式包含A.h。我见过只有两种情况使用前向引用:第一种是关于循环引用的上述情况,第二种是供应商提供的头文件中仅定义了某些类作为前向引用的情况。他们已经清理了头文件以保护其专有信息。结果是使用他们的API非常困难 - 我不建议这样做。 - Hitesh
我曾尝试过类似的事情..从接口(.h)中删除头文件并前向声明我要使用的类。 - Sii

3

像编译器一样思考。要在 B 中创建一个 A,编译器必须知道如何构建 A,而唯一的方法是具有完整的定义。前向声明告诉编译器类 A 存在,但没有描述其外观;这足以定义指针或引用。当使用该指针或引用时,将需要完整的类定义。


如果我在B.cpp文件中包含头文件"A.h",并且只在B.h中前向声明类A,那么它不应该可以工作吗? - Sii
@MrProg,不行 - B.h 定义了如何创建 B,但是你不能在不知道如何创建 A 的情况下创建 B,因为 B 包含 A。你可以存储对 A 的指针或引用而不知道如何创建 A,这就是为什么前向引用可能有用,只是在你的情况下不行。 - Mark Ransom
如果我将 A obj 更改为 A *obj 并按照上述所述的方法操作,并且将 class A 中的 int a 改为 public,我就可以编译了。但是我确实会得到分段错误。因此,我想我的前向声明理解需要重新考虑。 - Sii

2
如果您想将some_variable表现为指针,则通常建议使用前向声明尽可能避免包含的开销和更长的编译时间。
我完全赞同最佳实践,但我真的很喜欢使用具有良好代码导航功能的IDE,并且使用forward会在这方面造成问题,至少在Netbeans中是这样。每当我尝试导航到类型声明时,我总是会停留在前向声明而不是包含实际声明的.h文件上。为了导航的便利性,我愿意接受一些额外的编译时间。也许这只是Netbeans的问题 :)
哦对了,如果您查看问题旁边的相关问题,您将找到有关前向声明的大量其他信息。

这就是我想要避免的...不必要的头文件包含。 - Sii

0

对于情况1,编译器会因为类B包含一个类A对象且你没有告诉B关于类A的任何细节而报错“不完整类型”,所以编译器无法确定对象B的大小。

对于你的情况,你可以使用A& obj或者A* obj代替A obj,因为引用/指针的大小是固定的(32位/64位CPU分别为4/8字节)。


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