解决由于类之间的循环依赖导致的构建错误 [英] Resolve build errors due to circular dependency amongst classes

查看:28
本文介绍了解决由于类之间的循环依赖导致的构建错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常发现自己在 C++ 项目中面临多个编译/链接器错误,这是由于一些糟糕的设计决策(由其他人:))导致不同头文件中的 C++ 类之间的循环依赖(也可以发生在同一个文件中).但幸运的是(?)这种情况发生的频率并不高,我无法在下次再次发生时记住该问题的解决方案.

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

当您编译 .cc 文件时(请记住,.cc 而不是 .h 是编译单位),您需要为对象A 分配空间.那么,那么,有多少空间呢?足以存储B!那么 B 的大小是多少?足以存储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.

您可以通过允许编译器保留尽可能多的空间来打破它,例如,指针和引用将始终是 32 位或 64 位(取决于体系结构),因此如果您替换(或者一)通过指针或引用,事情会很棒.假设我们在 A 中替换:

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

现在情况好多了.有些.main() 仍然说:

Now things are better. Somewhat. main() still says:

// file: main.cc
#include "A.h"  // <-- Houston, we have a problem

#include,对于所有范围和目的(如果您去掉预处理器),只需将文件复制到 .cc.真的,.cc 看起来像:

#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;
}

你可以看到为什么编译器不能处理这个——它不知道 B 是什么——它以前甚至从未见过这个符号.

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.

所以让我们告诉编译器关于B.这称为前向声明,在这个答案.

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.

这个修复不好的原因是#include "Ah"的下一个人必须在使用它之前声明B并且会得到一个可怕的<代码>#include 错误.因此,让我们将声明移至 A.h 本身.

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.
};

而在B.h中,此时你可以直接#include "A.h".

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆