解决由于类之间的循环依赖关系而导致的生成错误 我经常发现我自己在一个C++项目中面临多个编译/链接错误的情况,因为一些糟糕的设计决定(由别人做的):这导致了在不同的头文件中C++类之间的循环依赖性(同样也可能发生在同一个文件中)。但幸运的是(?)这种情况发生的次数不多,我记不起下一次再次发生时这个问题的解决方案
因此,为了便于将来回忆,我将发布一个具有代表性的问题和解决方案。当然,我们欢迎更好的解决方案解决由于类之间的循环依赖关系而导致的生成错误 我经常发现我自己在一个C++项目中面临多个编译/链接错误的情况,因为一些糟糕的设计决定(由别人做的):这导致了在不同的头文件中C++类之间的循环依赖性(同样也可能发生在同一个文件中)。但幸运的是(?)这种情况发生的次数不多,我记不起下一次再次发生时这个问题的解决方案,c++,compiler-errors,circular-dependency,c++-faq,C++,Compiler Errors,Circular Dependency,C++ Faq,因此,为了便于将来回忆,我将发布一个具有代表性的问题和解决方案。当然,我们欢迎更好的解决方案 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 o
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; } };
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; }
#包括“B.h” #包括 int main(int argc,char*argv[]) { A(10); B(3.14); a、 打印(); a、 环境与预算局; b、 打印(); b、 刚毛; 返回0; }
//A.h
#ifndef A_H
#define A_H
class B;
class A
{
int _val;
B* _b;
public:
A(int val);
void SetB(B *b);
void Print();
};
#endif
//B.h
#ifndef B_H
#define B_H
class A;
class B
{
double _val;
A* _a;
public:
B(double val);
void SetA(A *a);
void Print();
};
#endif
//A.cpp
#include "A.h"
#include "B.h"
#include <iostream>
using namespace std;
A::A(int val)
:_val(val)
{
}
void A::SetB(B *b)
{
_b = b;
cout<<"Inside SetB()"<<endl;
_b->Print();
}
void A::Print()
{
cout<<"Type:A val="<<_val<<endl;
}
//B.cpp
#include "B.h"
#include "A.h"
#include <iostream>
using namespace std;
B::B(double val)
:_val(val)
{
}
void B::SetA(A *a)
{
_a = a;
cout<<"Inside SetA()"<<endl;
_a->Print();
}
void B::Print()
{
cout<<"Type:B val="<<_val<<endl;
}
//main.cpp
#include "A.h"
#include "B.h"
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;
}
//A.h
#如果没有
#定义一个
乙级;;
甲级
{
国际价值;
B*_B;
公众:
A(int-val);
无效收进(B*B);
作废打印();
};
#恩迪夫
//B.h
#ifndef B_H
#定义B_H
甲级;
B类
{
双重价值;
A*_A;
公众:
B(双val);
无效刚毛(A*A);
作废打印();
};
#恩迪夫
//A.cpp
#包括“A.h”
#包括“B.h”
#包括
使用名称空间std;
A::A(int-val)
:_val(val)
{
}
无效A::挫折(B*B)
{
_b=b;
不能记住的事情:
- 如果
class A
的对象是class B
的成员,或者反之亦然,则此操作不起作用
- 远期申报是一条路要走
- 声明的顺序很重要(这就是为什么要移出定义)。
- 如果两个类都调用另一个类的函数,则必须将定义移出
阅读常见问题:
思考这个问题的方法是“像编译器一样思考”
假设您正在编写一个编译器,您会看到这样的代码
// 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
你可以看到为什么编译器不能处理这个问题-它不知道B
是什么-它以前从未见过这个符号
因此,让我们告诉编译器关于B
。这被称为a,将在中进一步讨论
这是有效的。它不是很好。但是在这一点上,你应该了解循环引用问题,以及我们如何“修复”它,尽管修复是不好的
此修复不好的原因是,下一个包含“A.h”
的人必须先声明B
,然后才能使用它,并将得到一个可怕的\include
错误。因此,让我们将声明移到A.h本身
// file: A.h
class B;
class A {
B* _b; // or any of the other variants.
};
在B.h中,此时,您可以直接#包括“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.我曾经解决过这类问题,方法是将所有内联线移到类定义之后,并将其他类的
#include
放在头文件的内联线之前。这样可以确保在解析内联线之前设置所有定义+内联线
这样做使得在两个(或多个)头文件中仍然有一堆内联线成为可能
像这样
// File: A.h
#ifndef __A_H__
#define __A_H__
class B;
class A
{
int _val;
B *_b;
public:
A(int val);
void SetB(B *b);
void Print();
};
// Including class B for inline usage here
#include "B.h"
inline A::A(int val) : _val(val)
{
}
inline void A::SetB(B *b)
{
_b = b;
_b->Print();
}
inline void A::Print()
{
cout<<"Type:A val="<<_val<<endl;
}
#endif /* __A_H__ */
//文件:A.h
#如果没有__
#定义__
乙级;;
甲级
{
国际价值;
B*_B;
公众:
A(int-val);
无效收进(B*B);
作废打印();
};
//包括用于内联使用的B类
#包括“B.h”
内联A::A(int val):\u val(val)
{
}
内联void A::SetB(B*B)
{
_b=b;
_b->Print();
}
内联void A::Print()
{
我曾经写过一篇关于这个的帖子:
基本技术是使用接口将类解耦。因此在您的情况下:
//Printer.h
class Printer {
public:
virtual Print() = 0;
}
//A.h
#include "Printer.h"
class A: public Printer
{
int _val;
Printer *_b;
public:
A(int val)
:_val(val)
{
}
void SetB(Printer *b)
{
_b = b;
_b->Print();
}
void Print()
{
cout<<"Type:A val="<<_val<<endl;
}
};
//B.h
#include "Printer.h"
class B: public Printer
{
double _val;
Printer* _a;
public:
B(double val)
:_val(val)
{
}
void SetA(Printer *a)
{
_a = a;
_a->Print();
}
void Print()
{
cout<<"Type:B val="<<_val<<endl;
}
};
//main.cpp
#include <iostream>
#include "A.h"
#include "B.h"
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;
}
//Printer.h
类打印机{
公众:
虚拟打印()=0;
}
//A.h
#包括“Printer.h”
甲级:公用打印机
{
国际价值;
打印机*b;
公众:
A(int-val)
:_val(val)
{
}
无效设置(打印机*b)
{
_b=b;
_b->Print();
}
作废打印()
{
维基百科上的这个简单的例子对我很有用。
(您可以在上阅读完整的说明)
文件“'a.h'”:
文件''b.h'':
文件“main.cpp”:
我很晚才回答这个问题,但迄今为止还没有一个合理的答案,尽管这是一个很受欢迎的问题,答案都是高投票率的
最佳实践:转发声明头
如标准库的
标题所示,为其他人提供转发声明的正确方法是使用转发声明标题。例如:
a、 fwd.h:
#pragma once
class A;
a、 h:
b、 fwd.h:
#pragma once
class B;
template <typename T> class Basic_B;
typedef Basic_B<char> B;
#ifndef A_H
#define A_H
class B; //forward declaration
class A {
public:
B* b;
};
#endif //A_H
#ifndef B_H
#define B_H
class A; //forward declaration
class B {
public:
A* a;
};
#endif //B_H
#include "a.h"
#include "b.h"
int main() {
A a;
B b;
a.b = &b;
b.a = &a;
}
#pragma once
class A;
#pragma once
#include "a.fwd.h"
#include "b.fwd.h"
class A
{
public:
void f(B*);
};
#pragma once
class B;
#pragma once
#include "b.fwd.h"
#include "a.fwd.h"
class B
{
public:
void f(A*);
};
template <typename T> class Basic_B;
typedef Basic_B<char> B;
template <typename T>
class Basic_B
{
...class definition...
};
typedef Basic_B<char> B;
// A_def.h
#ifndef A_DEF_H
#define A_DEF_H
class B;
class A
{
int _val;
B *_b;
public:
A(int val);
void SetB(B *b);
void Print();
};
#endif
// B_def.h
#ifndef B_DEF_H
#define B_DEF_H
class A;
class B
{
double _val;
A* _a;
public:
B(double val);
void SetA(A *a);
void Print();
};
#endif
// A.h
#ifndef A_H
#define A_H
#include "A_def.h"
#include "B_def.h"
inline A::A(int val) :_val(val)
{
}
inline void A::SetB(B *b)
{
_b = b;
_b->Print();
}
inline void A::Print()
{
cout<<"Type:A val="<<_val<<endl;
}
#endif
// B.h
#ifndef B_H
#define B_H
#include "A_def.h"
#include "B_def.h"
inline B::B(double val) :_val(val)
{
}
inline void B::SetA(A *a)
{
_a = a;
_a->Print();
}
inline void B::Print()
{
cout<<"Type:B val="<<_val<<endl;
}
#endif
// file: a.h
#include "b.h"
struct A {
A(const B& b) : _b(b) { }
B get() { return _b; }
B _b;
};
// note that the get method of class B is defined in a.h
A B::get() {
return A(*this);
}
// file: b.h
class A;
struct B {
// here the get method is only declared
A get();
};
// file: main.cc
#include "a.h"
int main(...) {
B b;
A a = b.get();
}
// A.h
#pragme once
#include "B.h"
class A{
B b;
inline void Do(B b);
}
inline void A::Do(B b){
//do something with B
}
// B.h
#pragme once
class A;
class B{
A* b;
inline void Do(A a);
}
#include "A.h"
inline void B::Do(A a){
//do something with A
}
//main.cpp
#include "A.h"
#include "B.h"
//main.cpp
//#include "A.h"
class A;
class B{
A* b;
inline void Do(A a);
}
inline void B::Do(A a){
//do something with A
}
class A{
B b;
inline void Do(B b);
}
inline void A::Do(B b){
//do something with B
}
//#include "B.h"
//main.cpp
class A;
class B{
A* b;
inline void Do(A a);
}
class A{
B b;
inline void Do(B b);
}
inline void B::Do(A a){
//do something with A
}
inline void A::Do(B b){
//do something with B
}