「实战总结」头文件循环引用问题

文章讨论了在C++编程中遇到的头文件循环引用问题,以及如何通过仅引用class而非完整包含头文件来解决这个问题。文中以Master和Worker类为例,解释了为何在worker.h中更改引用方式可以避免编译错误,同时强调了在使用STL如vector时需要注意的效率和内存管理问题,建议在构造时预留足够的容量以避免不必要的拷贝和内存移动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

I. Motivation

我们在工程中应该会经常遇到头文件循环引用的问题,如果不能解耦引用关系,那么就不能通过编译

何为循环引用?其实,就是头文件 1 包含了头文件 2 ,头文件 2 包含了头文件 1 。假设,现在局面是这样的,我们有两个 class ,分别为 Master 和 Worker 。前者负责向 Workers 分配计算任务,而后者负责具体的计算工作,如此分工明确

为了使 Master 和 Workers 之间能够双工通信,我们让 Master 和 Workers 都知道彼此的存在,体现在代码中,就是 master.h 中要引用 worker.h ,

/* in master.h */
#include "worker.h"

worker.h 中引用 master.h ,

/* in worker.h */
#include "master.h"

如果我们这样写,那么恭喜自己,顺利陷入引用的死循环中。该如何破局呢?

II. Solution

我们可以将其中的一个引用改成只引用 class ,比如变动 worker.h ,将,

/* in worker.h */
#include "master.h"

改成,

class Master;

而 master.h 中的引用保持不变,即可破局。代码如下,

/* in master.h */
#pragma once

#include <QObject>
#include "worker.h"

class Master : public QObject
{
	Q_OBJECT

private:
	std::vector<Worker> wos_;

public:
	Master();
	~Master();
};

在 worker.h 中引用 class ,

/* in worker.h */
#pragma once

#include <QObject>

/* 为了解决循环引用,用 class 来替换 #include ,转而在 .cpp 中正式 #include */
class Master;

class Worker : public QObject
{
	Q_OBJECT

private:
	Master* ma_;

public:
	Worker() {}
	~Worker() {}
	Worker(const Worker& wo) 
		: ma_(wo.ma_), id_(wo.id_) {}

	...
};

转而在 worker.c 中引用 master.h ,以便访问 Master 的方法和成员变量,

/* in worker.h */
#include "worker.h"
#include "master.h"

III. Evaluation

不知大家是否有留意,我为什么选择改动 worker.h 而不是 master.h ?其实,这其中也有道道可讲!不能随便替换的

class Master 有一个存放 Worker 的 vector ,在使用 vector 的时候,我们需要将 Worker 的所有代码都插入至 master.h 中,因为只有这样编译器才能继续正常解析。如果我们将 master.h 中,

#include "worker.h"

替换成,

class Worker;

那么,可能会导致 MSVC error C2036:未知的大小。翻译一下就是,我们没有告诉编译器 Worker 具体有多大,编译器在没看到 Worker 相关定义的代码时,无法判断。而光凭引用 class Worker 是无法确定大小的,因为这只是句声明而已

反观 worker.h ,在 Worker 中只有一个指向 Master 的 ma_ 指针,站在编译器的角度上,这已经足够了。编译器是知道的,在 32 位机器上,任何类型的指针都为 4 Byte ;在 64 位上为 8 Byte 。这些都是定数,所以也就不存在会再出现 MSVC error C2036:未知的大小

C++ 开发无法避开 STL ,可以说 STL 就是 C++ 的精髓和灵魂。使用 STL 需要很多的技巧,特别是在自定义结构体时,一定要写清楚结构体的三巨头,即是构造、析构和拷贝构造函数。C++11 引用 move 之后,还需要写右值移动构造函数,还有拷贝赋值函数

总之,C++ STL 的工作方式,就是拷贝进,拷贝出。太多的技巧,尽在 「经典研读」Effective STL

在本文的案例中,Master 在构造函数中应该为 wos_ 预留足够的空间,而不是选择让其不断地扩容。原因有二,其一,频繁的扩容会带来不必要的拷贝,降低效率;其二,元素会搬家,从旧内存移动新内存,这可能会导致以前指向旧内存元素的指针失效。具体这么写,

wos_.reserve(NWORKER);	/* 预留空间,防止 vec 扩容 */

for (unsigned i = 0; i < NWORKER; i++) 
  wos_.emplace_back(ma_, i);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值