我经常发现自己在 C++ 项目中面临多个编译/链接器错误,这是由于一些糟糕的设计决策(由其他人做出的 :))导致不同头文件中 C++ 类之间的循环依赖关系< em>(也可以发生在同一个文件中)。但幸运的是(?)这种情况并不经常发生,以至于我在下次再次发生时记住这个问题的解决方案。
因此,为了方便以后记忆,我将发布一个有代表性的问题和解决方案。当然欢迎更好的解决方案。
思考这个问题的方法是“像编译器一样思考”。
假设您正在编写一个编译器。你会看到这样的代码。
// file: A.h
class A {
B _b;
};
// file: B.h
class B {
A _a;
};
// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
A a;
}
当您编译 .cc 文件时(请记住,.cc 而不是 .h 是编译单元),您需要为对象 A
分配空间。那么,那么,有多少空间呢?足以存储B
!那么 B
的大小是多少呢?足以存储A
!哎呀。
显然是你必须打破的循环引用。
您可以通过允许编译器保留尽可能多的预先知道的空间来打破它 - 例如,指针和引用将始终为 32 或 64 位(取决于体系结构),因此如果您替换(或者一)通过指针或引用,事情会很棒。假设我们在 A
中替换:
// file: A.h
class A {
// both these are fine, so are various const versions of the same.
B& _b_ref;
B* _b_ptr;
};
现在情况好多了。有些。 main()
还是说:
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
,对于所有范围和目的(如果您去掉预处理器)只需将文件复制到 .cc 中。真的,.cc 看起来像:
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}
你可以看到为什么编译器不能处理这个 - 它不知道 B
是什么 - 它甚至从未见过这个符号。
让我们告诉编译器关于B
。这被称为 forward declaration , 并在 this answer 中进一步讨论.
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
这有效。这不是伟大。但是此时您应该了解循环引用问题以及我们为“修复”它所做的工作,尽管修复很糟糕。
这个修复不好的原因是因为 #include "A.h"
的下一个人必须在使用它之前声明 B
并且会得到一个糟糕的 #include
错误。因此,让我们将声明移入 A.h 本身。
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
而在B.h中,此时可以直接#include "A.h"
。
// file: B.h
#include "A.h"
class B {
// note that this is cool because the compiler knows by this time
// how much space A will need.
A _a;
}
HTH。
我是一名优秀的程序员,十分优秀!