XXX
分类: C/C++
2014-03-31 10:50:39
原文地址:有关“类前置声明”和“包含头文件”的相关问题 作者:自语的骆驼
首先,文章主要内容是通过学习其它博客的总结。
以前一直没有注意过这方面的问题,总是认为.cpp中有新引入的类时,只要将其头文件在.h文件前端引入即可。这种做法比.h文件中前置声明引入类,.cpp文件中引入头文件的方式方便很多。但实际上,这是非常不好的习惯,因为这种方式会影响编译效率。
对类做前置声明(forward declaration)会告诉编译器,我们用到的这些类已经存在了,并且不需要知道这些类的完整定义。我们为什么要这样做,而不是将它们的头文件包含进来呢?这主要是由于在程序下文中,我们只是简单的定义了指向这些类的对象的指针,而并没有涉及到该类的其他方面。(如果有使用类的相关成员,那么单单做类的前置声明是编译不过的)
这样做的好处:
一 避免了头文件被其他文件多次包含,尤其是在头文件中包含头文件时,容易造成重复包含和产生包含顺序问题,并且增大了文件的体积;
二 提高了编译速度,因为编译器只需知道该类已经被定义了,而无需了解定义的细节。
三 在增量编译时会减少编译文件数量。如:在头文件h1中包含头文件h2,那么如果h2有一点改动时,编译器认为h1同样有改动,所以编译文件数量会有增加。
尽量不要在头文件中包含另外的头文件
一种好的编程风格是,尽量在头文件中使用类前置声明程序下文中要用到的类,实在需要包含其它的头文件时,可以把它放在我们的类实现文件中。
==========================分隔线===========================
==========================分隔线===========================
如下转自:http://blog.csdn.net/clever101/archive/2009/10/31/4751717.aspx
对前置声明和包含头文件使用情况分析:
类的前置声明(forward declaration)和包含头文件(#include)的区别常常会迷惑我们,特别是涉及两个类相互包含的时候。因此我们有必要搞清楚二者的区别以及二者的适用场合。
首先我们需要问一个问题是:为什么两个类不能互相包含头文件?所谓互相包含头文件,我举一个例子:我实现了两个类:图层类CLayer和符号类CSymbol,它们的大致关系是图层里包含有符号,符号里定义一个相关图层指针,具体请参考如下代码(注:以下代码仅供说明问题,不作为类设计参考,所以不适宜以此讨论类的设计,编译环境为Microsoft Visual C++ 2005,,Windows XP + sp2,以下同):
现在开始编译,编译出错,出错信息如下:
1>正在编译...
1>TestUnix.cpp
1>f:\mytest\mytest\src\testunix\symbol.h(14) : error C2143: 语法错误: 缺少“;”(在“*”的前面)
1>f:\mytest\mytest\src\testunix\symbol.h(14) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int
1>f:\mytest\mytest\src\testunix\symbol.h(14) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int
1>Layer.cpp
1>f:\mytest\mytest\src\testunix\symbol.h(14) : error C2143: 语法错误: 缺少“;”(在“*”的前面)
1>f:\mytest\mytest\src\testunix\symbol.h(14) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int
1>f:\mytest\mytest\src\testunix\symbol.h(14) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int
1>Symbol.cpp
1>f:\mytest\mytest\src\testunix\layer.h(18) : error C2143: 语法错误: 缺少“;”(在“*”的前面)
1>f:\mytest\mytest\src\testunix\layer.h(18) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int
1>f:\mytest\mytest\src\testunix\layer.h(18) : error C4430: 缺少类型说明符- 假定为int。注意: C++ 不支持默认int
现在让我们分析一下编译出错信息(我发现分析编译信息对加深程序的编译过程的理解非常有好处)。首先我们明确:编译器在编译文件时,遇到#include "x.h"时,就打开x.h文件进行编译,这相当于把x.h文件的内容放在#include "x.h"处。编译信息告诉我们:它是先编译TestUnix.cpp文件的,那么接着它应该编译stdafx.h,接着是Layer.h,如果编译Layer.h,那么会编译Symbol.h,但是编译Symbol.h又应该编译Layer.h啊,这岂不是陷入一个死循环? 呵呵,如果没有预编译指令,是会这样的,实际上在编译Symbol.h,再去编译Layer.h,Layer.h头上的那个#pragma once就会告诉编译器:老兄,这个你已经编译过了,就不要再浪费力气编译了!那么编译器得到这个信息就会不再编译Layer.h而转回到编译Symbol.h的余下内容。当编译到CLayer *m_pRelLayer;这一行编译器就会迷惑了:CLayer是什么东西呢?我怎么没见过呢?那么它就得给出一条出错信息,告诉你CLayer没经定义就用了呢?在TestUnix.cpp中#include "Layer.h"这句算是宣告编译结束(呵呵,简单一句弯弯绕绕不断),轮到#include "Symbol.h",由于预编译指令的阻挡,Symbol.h实际上没有得到编译,接着再去编译TestUnix.cpp的余下内容。
当然上面仅仅是我的一些推论,还没得到完全证实,不过我们可以稍微测试一下,假如在TestUnix.cpp将#include "Layer.h"和#include "Symbol.h"互换一下位置,那么会不会先提示CSymbol类没有定义呢?实际上是这样的。当然这个也不能完全证实我的推论。
照这样看,两个类的互相包含头文件肯定出错,那么如何解决这种情况呢?一种办法是在A类中包含B类的头文件,在B类中前置盛明A类,不过注意的是B类使用A类变量必须通过指针来进行。通过分析这个实际上我们可以得出前置声明和包含头文件的区别。