C++头文件交叉引用问题 #include

4

假设我有两个类:AB;一个头文件 myheader.h

....
class A{
public:
    void display(B* c);
};
class B{
public:
    void display(A* c);
};
.....

编译器报错:'B'未被声明(在A::display中)这是预料中的。

于是我为AB编写了两个分离的头文件:aheader.h(包括类A的定义)和bheader.h(包括类B);

aheader.h中:

#include "bheader.h"

bheader.h 文件中:
#include "aheader.h"

进展顺利。

但是当我开始编写implementation.cpp时,问题就出现了:

#include "aheader.h"
#include "bheader.h"
void A::display(B* c){}
void B::display(A* c){}

现在,在B::display中 A'尚未被声明

不知道如何简要描述这个问题。

我正在使用Ubuntu14,Eclipse CDT,Linux GCC和Gnu Makd Builder。

我是C++的新手,我猜想这个问题发生在链接时。我真的希望有人能给我一个解释或解决方案。谢谢!


听起来像是声明依赖问题,但从你上面提供的信息中无法完全确定问题。你需要列出完整的文件或至少提供一个能够展示问题的示例。 - Component 10
嗯,@Component10,我建立了一个全新的项目,包括main.cpp文件,它与这两个类没有任何关系,还有一个myheader.h文件。一开始只有这两个文件。直到我遇到错误,然后我删除了这个myheader.h文件,并写了aheader.h和bheader.h以及implementation.cpp文件。现在我有四个文件,包括main.cpp(仍然没有做任何事情),我得到了相同的错误。 - gone
4个回答

14

当开始学习C/C++时,这种情况非常普遍。

C++中通常的方法是将所有不相关的类分开,每个类在自己的头文件中。头文件必须始终(我是说,始终)受到递归包含的保护。这是通过在头文件开头添加IFDEF来完成的,因此文件只被包含一次:

例如,文件aheader.h:

#ifndef __AHEADER_H__
#define __AHEADER_H__

//All the header code comes here...

#endif

所以在包含头文件时,编译器将执行以下操作:
  1. 是否定义了__AHEADER_H__
  2. 还没有定义,因此编译头文件。
  3. 编译器现在定义了__AHEADER_H__
  4. 如果在任何其他点再次包含该文件:
  5. 是否定义了__AHEADER_H__
  6. 是的,不要重新编译头文件。
好的,这是你问题的一部分。但让我们来看看你真正的问题。
你遇到错误是因为,正如编译器告诉你的那样,另一个类没有被定义。换句话说,编译器不知道A(或B)是什么。 你不能在“bheader.h”中包含“aheader.h”,因为这会引发“未定义B”错误:
  1. bheader将包括aheader
  2. aheader开始被处理
  3. 找到A类,其中包含一个具有B参数的方法。
  4. B是什么?
  5. 错误在这里!
当然,如果你以相同的解释方式反向包含它们(在aheader.h中包含bheader.h),错误将变成“A未定义”。
解决方案?
只需前向定义类:
在bheader.h中:
```c++ class A; // forward declaration
class B { public: void myMethod(A a); }; ```
在aheader.h中:
```c++ class B; // forward declaration
class A { public: void myMethod(B b); }; ```
#ifndef __BHEADER_H__
#define __BHEADER_H__

class A; //Forward definition. We don't have any member definition, just we are telling 
         //the compiler "Trust me, this is a class"

class B{
public:
   void display(A* c);
}
#endif

在 aheader.h 文件中也需要完成同样的操作。

最后,为了能够在你的实现代码中使用 A 或 B ,你应该包含该类的实际头文件。

文件 implementation.cpp:

#include "aheader.h"
#include "bheader.h"
void A::display(B* c){}
//And so on...

当你不需要完整的类定义时,可以使用前向声明 (即):

  • 当一个类仅用作参数或函数的返回值时。
  • 当一个类仅与指针一起使用时。

如果需要完整的类定义,则不能使用前向声明 (即):

  • 当作为类成员使用时。
  • 当继承自该类时。

另外,分离类的常规方法是将其实现分离为每个类一个 .cpp 文件,在最后添加:

  • aheader.h
  • aheader.cpp
  • bheader.h
  • bheader.cpp

8

不要相互包含头文件,而是使用前向声明。在 aheader.h 中:

class B;

class A{
public:
    void display(B* c);
};

bheader.h中:

class A;

class B{
public:
    void display(A* c);
};

为什么需要前向声明而不仅仅是一个? - milanHrabos
为什么需要前向声明和不只是一个? - undefined

1

首先,在每个类中,您需要保护包含(以便在编译时不会被包含多次)并预先声明作为函数参数的类型:

aheader.h

// This tells the precompiler only to incorporate this section if AHEADER_H is
// not defined so it should only happen once, even if you include aheader.h
// from many places.
#ifndef AHEADER_H 
#define AHEADER_H

class B; // This tells the compiler that B is a class but it doesn't need to
         // know more than that now.

class A{
public:
    void display(B* c);
};

#endif

bheader.h

#ifndef AHEADER_H
#define AHEADER_H

class A;

class B{
public:
    void display(A* c);
};

不要包含其他文件,保持它们完全分开。然后,您应该能够以任何顺序包含它们。 接下来,您可能需要为每个类保留单独的实现文件:

aimp.cpp

#include <aheader.h>
#include <bheader.h> // Compiler now needs B in full as this is the
                     // implementation

void A::display(B* c)
{
    // Some stuff
}

bimp.cpp

#include <aheader.h>
#include <bheader.h> // Same here - the order is not important now.

void B::display(A* c)
{
    // Some stuff
}

现在你可以创建第三个文件,比如说 main.cpp,内容如下:
#include <aheader.h>
#include <bheader.h>

int main()
{
    B b;
    A a;
    a.display(b);
    b.display(a);
    return 0;
}

最后一步是编译所有三个实现文件(.cpp),并将它们链接在一起成为可执行文件。祝你好运。

2
哇!非常感谢您详细的回答!所以解决方案很简单:分离实现!哈哈! - gone

0

这是因为当您包含一个文件时,它将被复制到源文件中。在第一次包含后,您的implementation.cpp将如下所示(假定您已经使用了包含保护):

#ifndef _AHEADER_H_
#define _AHEADER_H_

#include "bheader.h" 

class A{ public:
    void display(B* c); 
};
#endif //_AHEADER_H_

#include "bheader.h"

void A::display(B* c){}
void B::display(A* c){}

第二个 include 之后

#ifndef _AHEADER_H_
#define _AHEADER_H_

#ifndef _BHEADER_H_
#define _BHEADER_H_

#include "bheader.h" 

class B{ public:
    void display(A* c); 
};
#endif //_BHEADER_H_

class A{ public:
    void display(B* c); 
};
#endif //_AHEADER_H_

#include "bheader.h"

void A::display(B* c){}
void B::display(A* c){}

第三个包含文件也会包含bheader.h,但是你的包含保护宏将阻止其被执行。
就像在B类的声明中所看到的那样,在A类之前并没有声明。如果你想在同一个文件中定义两个类,可以这样做;
//File class_declarations.h

#ifndef _CLASS_DECLARATIONS_H_
#define _CLASS_DECLARATIONS_H_
class B;

class A{ public:
    void display(B* c); 
};

class B{ public:
    void display(A* c); 
};

#endif //_CLASS_DECLARATIONS_H_

现在编译器知道它将在后面遇到一个B类。

请注意,如果您尝试使用确切的类对象而不是指针,例如

class A{ public:
    void display(B c); 
};

仅有前向声明是无济于事的。有其他方法可以克服这个问题。


"提前声明没有什么好处。"我倾向于同意这一点,即使它可以解决问题,即使我以前不知道它。就像上面Component10所说的那样,为每个类拥有一个单独的实现将会很好地完成工作。非常感谢大家! - gone
即使您将实现文件分开,仍然需要进行前向声明。但是大多数情况下,将不同的类分开是更好的做法。顺便说一下,我所说的“前向声明没有好处”是指C++接受指针类型的前向声明。但是,前向声明对象的类仍然不被C++接受。这里有一个例子。 - ifyalciner
是的,我曾经认为如果我分离实现,就可以避免使用前向声明,但事实证明这是错误的。所以最好的做法,我想,是避免以这种方式设计类。 - gone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接