解析头包括循环依赖 [英] Resolve header include circular dependencies
问题描述
所以为了方便在未来的召回中,我将发表一个代表性的问题和解决方案。当然欢迎更好的解决方案。
-
Ah
B类;
class A
{
int _val;
B * _b;
public:
A(int val)
:_val(val)
{
}
void SetB * b)
{
_b = b;
_b-> Print(); // COMPILER ERROR:C2027:use of undefined type'B'
}
void Print()
{
cout< << _val<< endl;
}
};
-
Bh
#includeAh
class B
{
double _val;
A * _a;
public:
B(double val)
:_val(val)
{
}
void SetA * a)
{
_a = a;
_a-> Print();
}
void Print()
{
cout<<Type:B val =<< _val<< endl;
}
};
-
main.cpp
#includeBh
#include< iostream>
int main(int argc,char * argv [])
{
A a(10);
B b(3.14);
a.Print();
a.SetB(& b);
b.Print();
b.SetA(& a);
return 0;
}
想一想这种方式是像编译器一样思考。
想象一下,你正在编写一个编译器。你会看到这样的代码。
// file:A.h
class A {
B _b;
};
//文件:B.h
B类{
A _a;
};
//文件main.cc
#includeAh
#includeBh
int main(...){
A ;
}
在编译 .cc 请记住 .cc 而不是 .h 是编译单位),您需要为对象 A
。那么,那么多少空间呢?足够存储 B
!那么 B
的大小是多少?足够存储 A
!糟糕。
显然,必须破坏的循环引用。
您可以通过允许编译器而是保留尽可能多的空间,因为它知道前面的指针和引用,例如,将永远是32或64位(取决于架构),所以如果你用一个指针或引用替换(一个),事情将是伟大的。让我们假设我们在 A
中替换:
// file:Ah
class A {
//这两个都很好,所以各种const版本是一样的。
B& _b_ref;
B * _b_ptr;
};
现在事情更好了。有些。 main()
仍然说:
// file:main.cc
#includeAh//< - 休斯顿,我们有一个问题
#include
,对于所有的扩展和目的(如果你把预处理器出来)只是将文件复制到 .cc 。因此, .cc 看起来像这样:
//文件:partially_pre_processed_main.cc
A类{
B& _b_ref;
B * _b_ptr;
};
#includeB.h
int main(...){
A a;
}
你可以看到为什么编译器不能处理这个 - 它没有
因此,让我们告诉编译器 B c $ c> B
。这称为转发声明,将在此回答。
// main.cc
class B;
#includeA.h
#includeB.h
int main(...){
A a;
}
这工程。这不是很好的。但是在这一点上,你应该了解循环引用问题以及我们做了什么修复它,尽管修复是坏的。
这个修复的原因是坏是因为下一个人 #includeAh
必须声明 B
才能使用它,并且得到一个可怕的 #include
错误。因此,让我们将声明移动到 Ah 本身。
// file:Ah
B;
class A {
B * _b; //或任何其他变体。
};
在 Bh 中, $ c> #includeAh。
// file:Bh
#includeAh
class B {
//注意这是很酷,因为编译器知道这个时间
//空间A需要多少空间。
A _a;
}
HTH。
I often find myself in a situation where I am facing multiple compilation/linker errors in a C++ project due to some bad design decisions (made by someone else :) ) which lead to circular dependencies between C++ classes in different header files (can happen also in the same file). But fortunately(?) this doesn't happen often enough for me to remember the solution to this problem for the next time it happens again.
So for the purposes of easy recall in the future I am going to post a representative problem and a solution along with it. Better solutions are of-course welcome.
A.h
class B; class A { int _val; B *_b; public: A(int val) :_val(val) { } void SetB(B *b) { _b = b; _b->Print(); // COMPILER ERROR: C2027: use of undefined type 'B' } void Print() { cout<<"Type:A val="<<_val<<endl; } };
B.h
#include "A.h" class B { double _val; A* _a; public: B(double val) :_val(val) { } void SetA(A *a) { _a = a; _a->Print(); } void Print() { cout<<"Type:B val="<<_val<<endl; } };
main.cpp
#include "B.h" #include <iostream> int main(int argc, char* argv[]) { A a(10); B b(3.14); a.Print(); a.SetB(&b); b.Print(); b.SetA(&a); return 0; }
The way to think about this is to "think like a compiler".
Imagine you are writing a compiler. And you see code like this.
// 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;
}
When you are compiling the .cc file (remember that the .cc and not the .h is the unit of compilation), you need to allocate space for object A
. So, well, how much space then? Enough to store B
! What's the size of B
then? Enough to store A
! Oops.
Clearly a circular reference that you must break.
You can break it by allowing the compiler to instead reserve as much space as it knows about upfront - pointers and references, for example, will always be 32 or 64 bits (depending on the architecture) and so if you replaced (either one) by a pointer or reference, things would be great. Let's say we replace in A
:
// file: A.h
class A {
// both these are fine, so are various const versions of the same.
B& _b_ref;
B* _b_ptr;
};
Now things are better. Somewhat. main()
still says:
// file: main.cc
#include "A.h" // <-- Houston, we have a problem
#include
, for all extents and purposes (if you take the preprocessor out) just copies the file into the .cc. So really, the .cc looks like:
// file: partially_pre_processed_main.cc
class A {
B& _b_ref;
B* _b_ptr;
};
#include "B.h"
int main (...) {
A a;
}
You can see why the compiler can't deal with this - it has no idea what B
is - it has never even seen the symbol before.
So let's tell the compiler about B
. This is known as a forward declaration, and is discussed further in this answer.
// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
A a;
}
This works. It is not great. But at this point you should have an understanding of the circular reference problem and what we did to "fix" it, albeit the fix is bad.
The reason this fix is bad is because the next person to #include "A.h"
will have to declare B
before they can use it and will get a terrible #include
error. So let's move the declaration into A.h itself.
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
And in B.h, at this point, you can just #include "A.h"
directly.
// 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.
这篇关于解析头包括循环依赖的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!