C++ #pragma once指令:保护C++头文件不被重复包含

发布时间 2023-08-11 15:01:16作者: 一杯清酒邀明月

一、#ifndef/#define/#endif指令的问题

  在C++中,头文件的作用就是将代码以模块的形式组织起来,便于复用和维护。但是,头文件很容易出现重复定义的问题。比如,某个头文件被多个源文件包含,这些源文件又有可能被其他源文件包含,那么就有可能出现一个头文件被重复包含的情况。这样就会导致编译器生成的目标文件中出现多个相同的目标代码,最终链接器又要处理这些相同的目标代码,浪费时间和空间。

  为了避免这个问题,C++程序员们想到了用宏来实现头文件保护。一般的做法是在头文件中加入下面3行代码:

1 #ifndef _XXX_H_
2 #define _XXX_H_
3 // 此处是头文件的内容
4 #endif // _XXX_H_

  其中,_XXX_H_是头文件的宏名,可以随便指定,只要不和其他宏名重复即可。这几行代码的意思是:

  • 如果没有定义过宏名 _XXX_H_,则定义它,并编译头文件的内容
  • 如果已经定义过,则跳过头文件的内容,避免重复编译

  但是,这种写法还是有一个问题,就是它依赖于宏的唯一性。如果程序中有多个宏名相同的头文件,那么就会出现重复编译的问题,链接器又要处理多个相同的目标代码。所以,在实际开发中,程序员们更倾向于使用 #pragma once 指令来解决头文件保护的问题。

二、#pragma once指令的作用

  #pragma once 是一种编译器指令,它可以确保同一个头文件不会被重复包含。这个指令的原理很简单,就是在头文件的开头加上一行代码 #pragma once 即可。编译器在遇到这个指令时,会先检查这个头文件是否已经被包含过,如果已经包含过,则直接跳过;否则,编译头文件的内容并标记为已包含。

示例代码:

1 #pragma once  // 这个头文件只会被编译一次
2 
3 #include <iostream>
4 
5 void foo()
6 {
7   std::cout << "Hello, world!" << std::endl;
8 }

三、#pragma once指令的优势

  通过以上分析,我们可以发现,使用 #pragma once 指令有如下的优势:

  • 编译速度更快:相比 #ifndef/#define/#endif 指令,编译器只需要检查一次,就能确定是否已经包含了这个头文件。这样,就节省了大量的编译时间。
  • 写法更简洁:只需要在头文件的开头加上一行代码即可,省去了写宏名的繁琐过程,代码也更加简洁。
  • 可读性更好:相比较于 #ifndef/#define/#endif 指令, #pragma once 指令的可读性更好,在代码的可读性方面有着更好的体现。

四、#ifndef/#define/#endif与#pragma once的比较

  那么,在实际开发中应该使用哪种头文件保护方式呢?下面是两种方式的对比:

方式优点缺点适用场景
#ifndef/#define/#endif 可移植性好,支持所有的C++编译器 写法繁琐,容易出错 多平台、跨编译器的项目
#pragma once 编译速度快,代码简洁易读 不是所有的编译器都支持 单一平台、同一编译器的项目

五、总结

  使用头文件可以将程序模块化,方便代码的复用和维护。在C++中,头文件保护是一项重要的任务,可以避免头文件被重复包含而导致的编译错误和链接器错误。对于“#ifndef/#define/#endif”和“#pragma once”两种保护方式,我们必须根据实际情况选择最适合自己项目的方式。

示例代码:

 1 // foo.h
 2 #pragma once
 3 
 4 void foo();
 5 
 6 // foo.cpp
 7 #include "foo.h"
 8 
 9 void foo()
10 {
11   std::cout << "Hello, world!" << std::endl;
12 }
13 
14 // main.cpp
15 #include "foo.h"
16 
17 int main()
18 {
19   foo();
20   return 0;
21 }