结论:两个类互相引用时,必须用前向声明(class x;)配合分离声明与定义,不能在头文件中互相#include,否则因不完整类型导致编译错误或警告(如c2079、c4150)。

直接说结论:两个类互相引用时,不能在两个头文件里都 #include 对方;必须用 class X; 前向声明 + 分离声明与定义,否则必然编译失败或出现警告(比如 warning C4150)。
为什么不能互相 #include 头文件
编译器展开头文件是线性的。假设 A.h 包含 #include "B.h",而 B.h 又包含 #include "A.h",即使有 #pragma once 或 #ifndef 保护,也会导致:在解析 A.h 时,B.h 中对 A 的使用(如 A a;)发生在 A 完整定义之前 —— 此时 A 是不完整类型(incomplete type),无法定义对象、调用构造/析构函数、取 sizeof。
常见报错现象包括:
error C2079: 'x' uses undefined class 'B'-
warning C4150: 删除指向不完整 'B' 类型的指针;没有调用析构函数(说明你在A的析构里delete memb;,但编译器根本没见过B的析构函数定义) - 链接时报
undefined reference to 'B::B()'(因为定义被挡在了未展开的头文件外)
正确组织方式:前向声明 + .cpp 中包含
核心原则:头文件只负责「让编译器知道某个类存在」,具体实现逻辑全部后移到 .cpp 文件中。
以类 A 和 B 互相持有对方指针为例:
A.h:
#pragma once
class B; // ✅ 前向声明,仅用于指针/引用声明
<p>class A {
public:
B* b_ptr;
A();
~A(); // 析构函数声明可以放这里
void useB();
};</p>
B.h:
#pragma once
class A; // ✅ 同样只前向声明
<p>class B {
public:
A* a_ptr;
B();
void useA();
};</p>
A.cpp:
#include "A.h"
#include "B.h" // ✅ 这里才真正需要 B 的完整定义
<p>A::A() : b_ptr(nullptr) {}
A::~A() { delete b_ptr; } // ✅ 现在能看见 B 的析构函数了
void A::useB() { if (b_ptr) b_ptr->useA(); }</p>
B.cpp:
#include "B.h"
#include "A.h" // ✅ 同理,这里才包含 A 的完整定义
<p>B::B() : a_ptr(nullptr) {}
void B::useA() { if (a_ptr) a_ptr->useB(); }</p>
什么时候必须用指针或引用,不能用值成员
如果一个类要直接包含另一个类的对象(即值语义:B b;),那它就必须在头文件里看到对方的完整定义 —— 这会立刻打破循环依赖。所以:
- 只要存在互相引用,至少一方必须用
B*或B&,不能用B - 若双方都坚持用值成员(
A a;+B b;),编译器根本无法确定内存布局(sizeof(A)依赖sizeof(B),反之亦然),直接报错 - 前向声明
class B;后,你只能:- 声明
B*、B&、B&& - 声明接受
B*或B&的函数参数 - 但不能:
- 定义
B b; - 调用
b.method() - 写
sizeof(B) - 在头文件里
new B或delete b_ptr
- 定义
- 声明
容易被忽略的关键点
最常踩的坑不是不会写前向声明,而是忘了析构和 delete 的时机:
-
delete b_ptr;必须写在A.cpp里,且#include "B.h"必须在它之前 —— 否则析构函数体里看到的是不完整的B,触发C4150 - 不要在头文件里写
extern class B;(这是过时写法,C++ 不支持这种 extern class 声明) - 不要在
.h里#include对方头文件,又同时写class B;—— 冗余且易引发定义冲突 - 如果某处真需要值语义(比如
std::vector<b></b>),那就得把整个B的定义暴露给A.h,此时应重构:提取公共接口、引入中间层,或确认是否真的需要双向强依赖
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











