c++ - 解决由于类之间的循环依赖而导致的构建错误

标签 c++ compiler-errors circular-dependency c++-faq

我经常发现自己处于这样的情况下,由于一些错误的设计决策(由其他人做出的:),导致C++项目中出现多个编译/链接器错误,从而导致不同头文件中C++类之间的循环依赖(也可能发生)在同一文件中)。但是幸运的是(?)发生的次数不够多,以至于我下次再次遇到该问题时仍记得该问题的解决方案。

因此,为了将来方便起见,我将发布一个具有代表性的问题及其解决方案。当然,更好的解决方案是受欢迎的。

  • 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;
    }
    
  • 最佳答案

    考虑这一点的方法是“像编译器一样思考”。

    假设您正在编写一个编译器。您会看到这样的代码。

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

    关于c++ - 解决由于类之间的循环依赖而导致的构建错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50726754/

    相关文章:

    c++ - 如果包含引用成员的类中缺少原始变量,为什么它仍然可以访问?

    c++ - 将 QLineEdit 对象的内容保存到字符串变量中 (C++)

    java - reader.nextLine() 出现 "cannot find symbol"错误

    gcc - 为什么Seabios无法使用-fno-inline进行编译

    xml - Perl - XML/HoH 中的循环依赖检查

    python - 为什么循环导入似乎在调用堆栈中更靠前,但在更靠下的位置引发 ImportError?

    c++ - 不同的功能取决于模板类中的类型

    c++ - 在 C++ 中查找最大出现次数

    compiler-errors - QNX momentics IDE中子文件夹中的src文件无法编译

    c++ - 如何初始化const循环引用成员