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);