必须加防护,否则编译直接报错,如 error: redefinition of 'class foo';因 #include 是文本复制,多次包含导致类重复定义,违反单一定义规则(odr)。

必须加防护,否则编译直接报错,比如 error: redefinition of 'class Foo'。
为什么类定义在头文件里必须防重复包含
因为 C++ 的 #include 是文本复制操作,不是“导入模块”。只要同一个头文件被多次拉进一个 .cpp 文件(哪怕间接包含),类定义就会出现两次,违反单一定义规则(ODR)。这不是警告,是硬性编译失败。
典型场景:
-
A.h包含了class Widget {};,又#include "B.h" -
B.h也#include "A.h" -
main.cpp同时#include "A.h"和#include "B.h"→Widget被定义两次
#pragma once 是最简方案,但要注意边界
在头文件第一行写 #pragma once,之后所有内容就只生效一次。它不依赖宏名,也不怕命名冲突,现代编译器(GCC 5+、Clang、MSVC)全部支持。
但要注意两个实际限制:
- 它识别的是“物理文件路径”,如果项目里有硬链接或符号链接指向同一头文件,某些旧版编译器可能失效
- 它对“内容相同但路径不同”的副本无效——比如你拷贝了一份
utils_v2.h,#pragma once不会把它和原utils.h当作同一个文件
示例:
#pragma once
class MyClass {
public:
void do_something();
};
#ifndef / #define / #endif 更稳妥,但宏名容易出错
这是 ISO 标准做法,兼容性无死角,连嵌入式小众编译器也能跑。核心是用预处理宏做开关,但宏名必须全局唯一。
常见错误:
- 手误写成
#ifndef MYCLASS_H却在别处也用了MYCLASS_H→ 两个头文件互相屏蔽 - 用简单名如
#ifndef UTILS_H,结果第三方库也有同名宏 → 编译器静默跳过你的头文件,报“未声明”而不是重定义 - 宏名含双下划线或以
_开头接大写字母(如_FOO_H)→ 触发保留标识符,行为未定义
推荐命名方式:PROJECTNAME_MODULENAME_H,全大写+下划线,比如 GRAPHICS_RENDERER_H。
正确写法:
#ifndef GRAPHICS_RENDERER_H
#define GRAPHICS_RENDERER_H
class Renderer {
public:
void render();
};
#endif // GRAPHICS_RENDERER_H
类里放 inline 函数或 constexpr 变量也要防护
很多人以为“只声明不定义”就安全,其实不然。头文件里如果写了 inline void foo() { } 或 constexpr int X = 42;,这些仍属于定义,重复包含照样触发 ODR 错误。
所以防护不是“防类”,而是“防整个头文件内容被展开多次”。只要头文件里有任何定义(非 extern 声明、非纯声明),就必须加 #pragma once 或 include guard。
顺带提醒:如果头文件里不小心写了 int global_counter = 0; 这种非 const 非 inline 的变量定义,即使加了防护,也会在多个 .cpp 中生成多个定义,链接时报 multiple definition —— 那是另一类问题,得用 extern 或定义移到 .cpp 里。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











