【问题标题】:C++ Forward Declaration Problem when calling Method调用方法时的C++前向声明问题
【发布时间】:2009-01-20 06:40:43
【问题描述】:

我有一个我认为与前向声明有关的问题,但也许不是。

以下是相关代码:

啊.h

#ifndef A_H_
#define A_H_

#include "B.h"

class A
{
    private:
        B b;

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

#endif /*A_H_*/

B.h

#ifndef B_H_
#define B_H_

#include "A.h"

class A;

class B
{
    private:
        A& a;

    public:
        B(A& a) : a(a) {}
    
        void foo() { /*a.bar();*/ } //doesn't compile
};

#endif /*B_H_*/

main.cpp

#include "A.h"

int main()
{
    A a;

    return 0;
}

问题似乎在于 A::bar() 的调用。程序成功编译,直到我尝试调用此方法,此时出现两个错误:

错误:不完整类型“struct A”的使用无效

错误:“结构 A”的前向声明

我认为这是因为 A::bar() 尚未定义或声明,因为两个标题相互引用。但是,我转发了声明的 A 类并且不知道我还需要做什么。我是 C++ 新手,所以请原谅我。我在网上其他任何地方都找不到这个问题的答案。一如既往,提前致谢!

【问题讨论】:

    标签: c++ compiler-construction declaration


    【解决方案1】:

    你有一个循环引用,所以你需要分开 B.h.尝试类似:

    B.h:

    #ifndef B_H_
    #define B_H_
    
    // don't include A.h here!
    
    class A;
    
    class B
    {
       private:
          A& a;
    
       public:
          B(A& a) : a(a) {}
    
          void foo();
    };
    
    #endif /*B_H_*/
    

    B.cpp:

    #include "B.h"
    #include "A.h"
    
    void B::foo() { a.bar(); } // now you're ok
    

    编辑:解释为什么需要将其拆分为两个文件:

    B 类包含对A 的引用,它可以是所谓的不完整 类型。你不能在它上面调用任何函数,因为编译器还不知道A 到底是什么——它只知道它是某种类。一旦你包含了 A.h(在 .cpp 文件中),那么A 就是一个完整的类型,你可以用它做任何你喜欢的事情。

    您不能将整个内容保存在一个头文件中,因为您会得到一个循环引用。您正在使用包含防护来防止无限循环,但是您得到了您不想要的东西。当你编译 main.cpp 时,看看编译器会得到什么,就像你以前一样:

    // #include "A.h" ==>
    #define A_H_
    
    // #include "B.h" ==>
    #define B_H_
    
    // #include "A.h" ==> nothing happens! (since A_H_ is already defined)
    
    class A;
    
    class B {
    private:
        A& a;
    
    public:
        B(A& a) : a(a) {}
    
        void foo() { a.bar(); } // <-- what the heck is A here?
                                //     it's not defined until below
    };
    
    class A {
    private:
       B b;
    
    public:
       A() : b(*this) {}
    
       void bar() {}
    };
    
    int main() {
        A a;
        return 0;
    }
    

    【讨论】:

    • 这会对 B::foo() 不再是内联函数产生不利影响
    • 我之前试过这个,但是,我忽略了将“#include 'A.h'”移动到 B.cpp 文件中!谢谢!但是,有什么方法可以做到这一点而不将类分成两个文件?
    • 因此,如果您愿意,可以将其声明为内联。
    • 害羞:也许我可以声明类和方法,然后将它们内联到同一个文件中。
    • 为了能够内联声明它,B::foo 的实现也需要在 h 文件中。
    【解决方案2】:

    #include&lt;file.h&gt; 行只会将行替换为file.h 的内容。因此,当计算机尝试编译您的main.cpp 时,它会将所有内容放在一起,如下所示。在你想使用 A::bar() 的地方,它还没有被定义。

    // result from #include "A.h"
    
    #ifndef A_H_
    #define A_H_
    
    // Now, #include "B.h" in A.h will get you the following
    #ifndef B_H_
    #define B_H_
    
    // here you include "A.h" again, but now it has no effect
    // since A_H_ is already defined
    
    class A;
    
    class B
    {
        private:
                A& a;
        public:
                B(A& a) : a(a) {}
                // Oops, you want to use a.bar() but it is not defined yet
                void foo() { /*a.bar();*/ } 
    };
    
    #endif /*B_H_*/
    
    class A
    {
        private:
                B b;
        public:
                A() : b(*this) {}
                void bar() {}
    };
    #endif /*A_H_*/
    
    // now this is your main function
    int main()
    {
        A a;
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      正如其他几个人所提到的,循环引用似乎是您的问题。另一个短语是“相互依赖”。但是,与其尝试找到正确的语法来编译和运行您的应用程序(我假设实际问题存在于一个比您发布的内容稍微高级的程序中),我鼓励您从一个对象来解决问题 -面向设计的立场。

      作为一般规则,应尽可能避免相互依赖。我之前在自己的代码中遇到过这个问题(这导致了几天的调试,扯掉我的头发,诅咒我的编译器),这就是我最终能够克服它的方法。我将提出一个我自己的问题的淡化版本,作为如何解决这个问题的具体例子,所以希望你能找出这一切背后的隐藏含义,最终一切都会变得有意义。

      假设我们有两个类:Data 和 DataAnalyzer

      Data 持有对 DataAnalyzer(用于分析数据)的引用,而 DataAnalyzer 持有对 Data(要分析的数据)的引用——相互依赖!为了消除这种依赖性,我们从 DataAnalyzer 中提取了一个接口(在 C++ 中,一个纯虚拟类),它定义了 DataAnalyzer 所需的公共方法/属性。它可能看起来像:

      class IAnalyzer
      {
      public:
          virtual void Analyze () = 0;
      };
      

      当我们定义DataAnalyzer时,我们这样做:

      class DataAnalyzer : public IAnalyzer
      {
      public:
          DataAnalyzer (Data* data);
      
          virtual void Analyze (); // defined in DataAnalyzer.cpp
      };
      

      数据看起来像:

      class Data
      {
      public:
          Data ();
      
          IAnalyzer* Analyzer;
      };
      

      在你的控制器类的某个地方,你可能有类似的东西:

      void main ()
      {
          Data*  data = new Data ();
          data->Analyzer = new DataAnalyzer (data);
      }
      

      现在,Data 是独立存在的(据它所知,IAnalyzer 不需要对 Data 的引用),只有 DataAnalyzer 依赖于 Data。如果你想继续,你可以继续去掉DataAnalyzer对Data的依赖,但为了简单的打破相互依赖,这样就足够了。

      警告:我没有编译测试过这段代码,所以它可能需要一些小的调整才能正确编译和运行。

      祝你好运!

      【讨论】:

        【解决方案4】:

        如果你真的希望 B::foo 是内联的,你可以在 B.h 中实现,虽然我不推荐它。

        B.h:

        #ifndef B_H_
        #define B_H_
        
        // Forward declaration of A.
        class A;
        
        class B
        {
        private:
            A& a;
        
        public:
            B(A& a) : a(a) {}
        
            void foo();
        };
        
        // Include definition of A after definition of B.
        #include "A.h"
        
        inline void B::foo()
        {
            a.bar();
        }
        
        #endif /*B_H_*/
        

        啊哈:

        // Include definition of B before definition of A.
        // This must be done before the ifndef A_H_ include sentry,
        // otherwise A.h cannot be included without including A.h first.
        #include "B.h"
        
        #ifndef A_H_
        #define A_H_
        
        class A
        {
        private:
            B b;
        
        public:
            A() : b(*this) {}
        
            void bar() {}
        };
        
        #endif /*A_H_*/
        

        【讨论】:

          【解决方案5】:

          在 B.h 中,您包括 A.h 以及前向声明 A。

          你需要将B.h分成B.h和B.cpp,或者去掉前向声明。

          PS 你也有一个循环依赖。 A.h 包括 B.h,反之亦然。不过,你的警卫发现了问题;)

          【讨论】:

            【解决方案6】:

            要添加到另一个答案(循环引用,这是正确答案),如果您来自 C#/Java,请了解 C++ 是不同的,因为文件是按顺序解析的(而不是作为一个整体来考虑) )。因此,您需要小心确保在使用之前定义所有内容,按照包含文件的实际顺序(和/或根据需要将功能分离到 .cpp 文件中)。

            【讨论】:

            • 文件没有“按顺序”解析;我的构建环境实际上显示它们被并行解析。此外,您将文件内的顺序编译与文件间的链接混淆了。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-11-17
            • 1970-01-01
            • 2022-01-21
            • 2013-04-12
            相关资源
            最近更新 更多