C++ 调用方法时的前向声明问题

问题描述 投票:0回答:6

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

相关代码如下:

啊啊

#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_*/

主.cpp

#include "A.h"

int main()
{
    A a;

    return 0;
}

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

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

错误:‘struct A’的前向声明

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

c++ declaration
6个回答
11
投票

你有一个循环引用,所以你需要将 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
的引用,它可以是所谓的 incomplete 类型。你不能在它上面调用任何函数,因为编译器还不知道
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;
}

1
投票

#include<file.h>
只会用
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;
}

1
投票

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

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

假设我们有两个类: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 是独立存在的(据其所知,IAalyzer 不需要对 Data 的引用),并且只有 DataAnalyzer 依赖于 Data。 如果你想继续,你可以继续删除DataAnalyzer对Data的依赖,但是为了简单地打破相互依赖,这应该足够了。

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

祝你好运!


1
投票

如果你真的希望 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_*/

0
投票

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

需要将B.h分成B.h和B.cpp,或者删除前向声明。

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


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.