实用经验 58 防止重复包含头文件

头文件重复包含,是每个程序员都遇到过的问题。头文件重复包含,可能会导致的错误包括:变量重定义,类型重定义及其他一些莫名其妙的错误。现在我们就讨论如何避免这些问题。

假设,我们的工程中存在a.h,b.h及c.cpp等3个文件。其中b.h中包含了a.h,而c.cpp又包含了a.h和b.h两个文件。文件代码如下所示。

/*  测试头文件重复包含问题 */
// File : a.h

#include <iostream.h>

// func_1 实现func_1函数名称打印。
void func_1()
{
	std::cout << "this is" << __FUNCTION__ << endl;
}

// File : b.h

#include "a.h"

// Func_2 实现Func_2函数名称打印。并调用func_1。
void Func_2()
{
   std::cout << "this is " << __FUNCTION__ << endl;
   std::cout << "this function called " << endl;
   func_1();
}

// File : c.cpp
#include "a.h"
#include "b.h"

int main()
{
    // ....
    return 0;
}

如果,你在VC++编译器中编译上述代码。在编译过程中,编译器会抛出“重复定义”错误。因为a.h被重复包含了2次。为了避免同一个文件被重复包含多次。C++提出了2种解决方案。他们是#ifndef方式和#pragman once方式。

方式1:

#ifndef __SOME_FILE_H__
#define __SOME_FILE_H__
...   // 一些声明语句
#endif

方式2:

#pragma once
...  // 一些声明语句

1. #ifndef方式

这种实现方式,通过预处理实现唯一检查。预处理器首先测试__SOME_FILE_H__预处理器变量是否未定义。如果__SOME_FILE_H__未定义,那么#ifndef测试成功,跟在#ifndef 后面的所有行都被执行,直到发现 #endif。相反,如果__SOME_FILE_H__已定义,那么#ifndef 指示测试为假,该指示和#endif指示间的代码都被忽略。

为了保证头文件在给定的源文件中只处理过一次,我们首先检测#ifndef。第一次处理头文件时,测试会成功,因为__SOME_FILE_H__还未定义。下一条语句定义了__SOME_FILE_H__。那样的话,如果我们编译的文件恰好又一次包含了该头文件。#ifndef 指示会发现__SOME_FILE_H__已经定义,并且忽略该头文件的剩余部分。

头文件应该含有保护符,即使这些头文件不会被其他头文件包含。编写头文件保护符并不困难,而且如果头文件被包含多次,它可以避免难以理解的编译错误。

当没有两个头文件定义和使用同名的预处理器常量时,这个策略相当有效。当有两个文件使用同一个宏,这个策略就失效了。当遇到这种问题时,一般有两种解决方案。

方案1:可以为定义在头文件里的实体(如类)命名预处理器变量,来避免预处理器变量重名的问题。例如:一个程序只能含有一个名为Sales_item的类。通过使用类名来组成头文件和预处理器变量的名字,可以使得很可能只有一个文件将会使用该预处理器变量。

方案2:为了保证宏的唯一性,我们可以采用google提供的解决方案,在定义宏时,宏名基于其所在的项目源代码数的全路径命名。宏命名格式为:

_<PROJECT>_<PATH>_<FILE>_H_

最佳实践

  • 在定义#ifndef测试宏时,宏名最好采用全大写字母表示。
  • 在定义测试宏时,最好采用google提供的解决方案。

2. #pragma once

这种方式一般又编译器提供,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次#pragma once用来防止某个头文件被多次include,#ifndef方式用来防止某个宏被多次定义。

#pragma once是编译相关,就是说这个编译系统上能用,但在其他编译系统不一定可以,也就是说移植性差,不过现在基本上已经是每个编译器都有这个定义了。

#ifndef,#define,#endif这是C++语言相关的,是C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式。

小心陷阱

  • 针对#pragma once,GCC已经取消对其的支持了。而微软的VC++却依然支持。
  • 如果写的程序需要跨平台,最好使用#ifndef方式,而避免使用#progma once方式。

3. #pragma once与 #ifndef的区别

#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的窘况。

#pragma once则由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。

请谨记

  • 为了避免重复包含头文件,建议在每个头文件时采用“头文件卫士”加以保护。头文件卫士有两种形式一种是#progma once,一种是#ifndef。
  • 如果你的程序需要跨平台,建议你使用#ifndef方式。因为这种方式仅与C++语言有关。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值