【问题标题】:Headers Including Each Other in C++C++ 中相互包含的标头
【发布时间】:2010-09-28 14:33:56
【问题描述】:

我是 C++ 新手,但我无法在网上找到这个(很可能是微不足道的)问题的答案。我在编译一些两个类相互包含的代码时遇到了一些麻烦。首先,我的#include 语句应该放在宏的内部还是外部?在实践中,这似乎并不重要。但是,在这种特殊情况下,我遇到了麻烦。将#include 语句放在宏之外会导致编译器递归并给我“#include 嵌套太深”的错误。这对我来说似乎很有意义,因为在调用 #include 之前没有完全定义任何类。然而,奇怪的是,当我尝试将它们放入其中时,我无法声明其中一个类的类型,因为它无法识别。本质上,这就是我要编译的内容:

啊.h

#ifndef A_H_
#define A_H_

#include "B.h"

class A
{
    private:
        B b;

    public:
        A() : b(*this) {}
};

#endif /*A_H_*/

B.h

#ifndef B_H_
#define B_H_

#include "A.h"

class B
{
    private:
            A& a;

    public:
        B(A& a) : a(a) {}
 };

#endif /*B_H_*/

main.cpp

#include "A.h"

int main()
{
    A a;
}

如果有什么不同,我使用的是 g++ 4.3.2。

为了清楚起见,一般来说,#include 语句应该放在哪里?我一直看到它们超出了宏的范围,但我清楚地描述的场景似乎打破了这个原则。提前感谢任何帮助者!如果我犯了任何愚蠢的错误,请允许我澄清我的意图!

【问题讨论】:

  • 类不包含任何内容。

标签: c++ recursion header include


【解决方案1】:

我认为“宏”是指#ifndef 包含警卫? 如果是这样,#includes 肯定应该进去。这是包含守卫存在的主要原因之一,否则你很容易像你注意到的那样以无限递归结束。

无论如何,问题是在您使用 A 和 B 类(在另一个类中)时,它们还没有被声明。看看#includes 被处理后的代码是什么样子的:

//#include "A.h" start
#ifndef A_H_
#define A_H_

//#include "B.h" start
#ifndef B_H_
#define B_H_

//#include "A.h" start
#ifndef A_H_ // A_H_ is already defined, so the contents of the file are skipped at this point
#endif /*A_H_*/

//#include "A.h" end

class B
{
    private:
            A& a;

    public:
            B(A& a) : a(a) {}
 };

#endif /*B_H_*/

//#include "B.h" end

class A
{
    private:
            B b;

    public:
            A() : b(*this) {}
};

#endif /*A_H_*/
//#include "A.h" end

int main()
{
    A a;
}

现在阅读代码。 B 是编译器遇到的第一个类,它包含一个A& 成员。 A 是什么?编译器还没有遇到任何A 的定义,所以会报错。

解决方法是对A进行前向声明。在B定义之前的某个时间点,添加一行class A;

这为编译器提供了必要的信息,即 A 是一个类。我们对此一无所知,但由于 B 只需要包含对它的引用,这就足够了。在 A 的定义中,我们需要 B 类型的成员(不是引用),所以这里 B 的整个定义必须是可见的。幸运的是,它是。

【讨论】:

    【解决方案2】:

    为了清楚起见,一般来说,#include 语句应该放在哪里?

    由于您提到的原因,在包含守卫中。

    对于您的其他问题:您需要前向声明至少一个类,例如像这样:

    #ifndef B_H_
    #define B_H_
    
    // Instead of this:
    //#include "A.h"
    
    class A;
    
    class B
    {
        private:
                A& a;
    
        public:
                B(A& a) : a(a) {}
     };
    
    #endif /*B_H_*/
    

    这仅适用于声明:一旦您真正使用A 的实例,您也需要定义它。

    顺便说一句,Nathan 说的是真的:你不能递归地将类实例放入彼此。这仅适用于实例的指针(或者,在您的情况下,引用)。

    【讨论】:

    • 请注意,您仍然不能让类相互包含。如果 A 包含 B,则 B 不能包含 A
    • 请注意,B 类仅包含对其对应的 A 类对象的引用。我不认为我正在尝试做的事情有什么问题。
    【解决方案3】:

    在这种情况下,我创建了一个通用标头,以包含在所有具有前向声明的源中:

    #ifndef common_hpp
    #define common_hpp
    
    class A;
    class B;
    
    #endif
    

    然后,如果只需要指向这些类的指针或引用,则单个类头文件通常不需要任何#include 来引用其他类。有一半的时间虽然这些标题中还有其他好东西,但至少循环引用的任何问题都可以通过 common.hpp 解决

    【讨论】:

      【解决方案4】:

      哎呀!我想我找到了一个解决方案,涉及将#include 语句放入类中并使用前向声明。因此,代码如下所示:

      #ifndef A_H_
      #define A_H_
      
      class B;
      
      #include "B.h"
      
      class A
      {
          private:
                  B b;
      
          public:
                  A() : b(*this) {}
      };
      
      #endif /*A_H_*/
      

      对于 B 类也是如此。它可以编译,但这是最好的方法吗?

      【讨论】:

      • 这行得通,但你通常希望你的#includes 在任何其他代码之前(除了包含警卫),所以将 B 的前向声明移到 #include 之后,所有都应该是好吧。是的,前向声明是解决这个问题的标准方法。
      • 不,这不是解决方案,您已经前向声明 B,然后还包括 B.h,这避免了前向声明。这是您要在 B.h 中转发声明的 A,不包含 A.h。为了在 A 中有一个 B 的实例,你必须包含 B.h.康拉德有正确的答案。
      【解决方案5】:

      一个好的软件设计中两个类之间的依赖关系可以绘制成一棵树。

      因此,C++ 不会让两个 .h 文件#include 彼此。

      【讨论】:

        【解决方案6】:

        我怀疑这可以做到。您不是在谈论递归地从彼此内部调用两个 函数,而是在递归地将两个 对象 放在另一个内部。想想把房子和房子的图片放在房子的图片上等等......这将占用无限量的空间,因为您将拥有无限数量的房子和图片。

        可以做的是让AB 中的每一个都包含彼此的指针或引用。

        【讨论】:

        • 是的,除了在他发布的代码中,A.b 是对 B 的引用,所以它只是在房子里面有房子,里面有房子的地址。这需要有限的空间。 ;)
        • 呃,当然反过来。 B.a 是对 A 的引用。
        【解决方案7】:

        一些编译器(包括 gcc)也支持 #pragma once 但是您问题中的“包含守卫”习语是通常的做法。

        【讨论】:

          猜你喜欢
          • 2012-01-21
          • 1970-01-01
          • 2021-08-06
          • 1970-01-01
          • 2022-01-04
          • 1970-01-01
          • 2017-03-23
          • 1970-01-01
          相关资源
          最近更新 更多