【问题标题】:C header file loopsC 头文件循环
【发布时间】:2010-09-13 21:12:39
【问题描述】:

我有几个头文件,归结为:

树.h:

#include "element.h"

typedef struct tree_
{
    struct *tree_ first_child;
    struct *tree_ next_sibling;
    int tag;
    element *obj;
    ....
} tree;

和元素.h:

#include "tree.h"

typedef struct element_
{
    tree *tree_parent;
    char *name;
    ...
} element;

问题是它们都相互引用,所以tree需要包含元素,元素需要包含tree。

这不起作用,因为要定义“树”结构,元素结构必须已知,但要定义元素结构,树结构必须已知。

如何解决这些类型的循环(我认为这可能与“前向声明”有关?)?

【问题讨论】:

    标签: c include-guards


    【解决方案1】:

    这里有很多答案都提到了“包括警卫”和“前向声明”,但他们都没有真正打算解决 OP 目前面临的问题。第三个“.h”文件绝对不是答案。如果使用得当,“包括警卫”可以打破“#include 循环”并最终导致更清洁的项目结构。如果您已经有两个,为什么还要为typedefs 创建另一个头文件?你的头文件应该是这样的:

    /* a.h - dependency of b.h */
    #ifndef _A_H
    #define _A_H
    
    #include "b.h"
    
    typedef struct a_p {
        b_t *b;
    } a_t;
    
    #endif // _A_H
    
    /* b.h - dependency of a.h */
    #ifndef _B_H
    #define _B_H
    
    typedef struct b_p b_t;
    
    /** 
     * !!!
     * to avoid recursion, only include "a.h" 
     * when "a.h" isn't included before
     */
    #ifndef _A_H
        #include "a.h"
        typedef struct b_p {
            a_t a;
        } b_t;
    #endif
    
    #endif // _B_H
    

    要同时使用这两个头文件,您只需要include 一个,即无条件包含另一个的那个(在本例中为a.h)。但如果你愿意,你也可以包括“b.h”。但无论如何它不会有任何区别(由于前向声明)。

    #include "a.h"
    
    int main() {
        a_t aigh;
    
        return 0;
    }
    

    瞧!就是这个!没有多余的includes 没有。我们得到了 em bois!

    【讨论】:

      【解决方案2】:

      一个简单的解决方案是不要有单独的头文件。毕竟,如果它们相互依赖,你永远不会在没有另一个的情况下使用一个,那么为什么要分开它们呢?您可以拥有单独的 .c 文件,它们都使用相同的标头但提供更集中的功能。

      我知道这并不能回答如何正确使用所有花哨的东西的问题,但是当我在寻找类似问题的快速解决方案时,我发现它很有帮助。

      【讨论】:

        【解决方案3】:

        我不喜欢前向声明,因为它们是多余的和错误的。如果您希望所有声明都在同一个地方,那么您应该使用包含保护的包含和头文件。

        您应该将包含视为一种复制粘贴,当 c 预处理器找到 #include 行时,只需将 myheader.h 的全部内容放在找到 #include 行的相同位置。

        好吧,如果您编写包含保护,myheader.h 的代码将仅在找到第一个 #include 的位置粘贴一次。

        如果您的程序使用多个目标文件进行编译并且问题仍然存在,那么您应该在目标文件之间使用前向声明(就像使用 extern),以便只保留所有目标文件的类型声明(编译器将所有声明混合在同一个表中并且标识符必须是唯一的)。

        【讨论】:

          【解决方案4】:

          前向声明是您可以保证会有一个稍后定义的结构的方式。

          【讨论】:

            【解决方案5】:

            这里的关键观察是元素不需要知道树的结构,因为它只保存一个指向它的指针。树也是一样。每个人都需要知道的是存在具有相关名称的类型,而不是其中的内容。

            所以在 tree.h 中,而不是:

            #include "element.h"
            

            做:

            typedef struct element_ element;
            

            这“声明”了“element”和“struct element_”类型(说它们存在),但没有“定义”它们(说它们是什么)。您需要存储一个指向 blah 的指针是声明 blah,而不是定义它。只有当您想尊重它(例如阅读成员)时,您才需要定义。 “.c”文件中的代码需要这样做,但在这种情况下,你的标题不需要。

            有些人创建一个单独的头文件,它前向声明一组头文件中的所有类型,然后每个头文件都包含它,而不是确定它真正需要的类型。这既不是必要的,也不是完全愚蠢的。

            关于包含守卫的答案是错误的 - 总的来说,它们是个好主意,您应该阅读它们并自己了解一下,但它们并不能特别解决您的问题。

            【讨论】:

              【解决方案6】:

              我认为这里的问题不是缺少包含保护,而是两个结构在定义中相互需要。所以这是一个类型定义 hann 和 egg 问题。

              在 C 或 C++ 中解决这些问题的方法是对类型进行前向声明。如果你告诉编译器元素是某种结构,编译器就能生成指向它的指针。

              例如

              在tree.h里面:

              // tell the compiler that element is a structure typedef:
              typedef struct element_ element;
              
              typedef struct tree_ tree;
              struct tree_
              {
                  tree *first_child;
                  tree *next_sibling;
                  int tag;
              
                  // now you can declare pointers to the structure.
                  element *obj;
              };
              

              这样您就不必再将 element.h 包含在 tree.h 中了。

              你也应该在你的头文件周围加上包含保护。

              【讨论】:

                【解决方案7】:

                正确的答案是使用包含守卫,并使用前向声明。

                包括警卫

                /* begin foo.h */
                #ifndef _FOO_H
                #define _FOO_H
                
                // Your code here
                
                #endif
                /* end foo.h */
                

                Visual C++ 还支持#pragma once。它是一个非标准的预处理器指令。作为编译器可移植性的交换,您可以减少预处理器名称冲突的可能性并提高可读性。

                前向声明

                转发声明你的结构。如果没有明确需要结构或类的成员,您可以在头文件的开头声明它们的存在。

                struct tree;    /* element.h */
                struct element; /* tree.h    */
                

                【讨论】:

                  【解决方案8】:

                  恕我直言,最好的方法是避免这种循环,因为它们是应该避免的物理耦合的迹象。

                  例如(据我所知)"Object-Oriented Design Heuristics" 的目的是避免包含守卫,因为它们只会掩盖循环(物理)依赖关系。

                  另一种方法是像这样预先声明结构:

                  element.h:
                  struct tree_;
                  struct element_
                    {
                      struct tree_ *tree_parent;
                      char *name;
                    };

                  树.h: 结构元素_; 结构树_ { 结构树_* first_child; 结构树_* next_sibling; 整数标签; 结构元素_ *obj; };

                  【讨论】:

                    【解决方案9】:

                    了解forward declarations

                    即。

                    
                    // tree.h:
                    #ifndef TREE_H
                    #define TREE_H
                    struct element;
                    struct tree
                    {
                        struct element *obj;
                        ....
                    };
                    
                    #endif
                    
                    // element.h:
                    #ifndef ELEMENT_H
                    #define ELEMENT_H
                    struct tree;
                    struct element
                    {
                        struct tree *tree_parent;
                        ...
                    };
                    #endif
                    

                    【讨论】:

                      【解决方案10】:

                      包含守卫很有用,但不要解决发布者的问题,即递归依赖于两个数据结构。

                      这里的解决方案是将树和/或元素声明为头文件中结构的指针,因此您不需要包含 .h

                      类似:

                      struct element_;
                      typedef struct element_ element;
                      

                      tree.h 的顶部应该足以消除包含 element.h 的需要

                      使用这样的部分声明,您只能使用不需要编译器了解布局的元素指针来执行操作。

                      【讨论】:

                        【解决方案11】:
                        猜你喜欢
                        • 2011-07-11
                        • 2015-07-25
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-06-29
                        • 2011-01-06
                        • 2011-06-16
                        相关资源
                        最近更新 更多