解决由于类之间的循环依赖关系而导致的生成错误 我经常发现我自己在一个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;
    }
    

如果从头文件中删除方法定义,并让类仅包含方法声明和变量声明/定义,则可以避免编译错误。方法定义应该放在.cpp文件中(就像最佳实践指南所说的那样)

以下解决方案的缺点是(假设您已将方法放置在头文件中以内联它们),编译器不再内联这些方法,并且尝试使用内联关键字会产生链接器错误

//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
}