解析头包括循环依赖 [英] Resolve header include circular dependencies

查看:221
本文介绍了解析头包括循环依赖的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常发现自己在一个情况下,我面临着一个C ++项目中的多个编译/链接器错误,由于一些糟糕的设计决定(由别人:)),导致在不同的头文件中的C ++类之间的循环依赖性(也可以在同一个文件中发生)。但幸运的是(?)这不会发生经常足以让我记住下一次再次发生这个问题的解决方案。



所以为了方便在未来的召回中,我将发表一个代表性的问题和解决方案。当然欢迎更好的解决方案。







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

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