伟大的 c++ 前向声明混乱

The great c++ forward declaration confusion

本文关键字:声明 混乱 c++ 伟大      更新时间:2023-10-16

考虑我有一个类A和一个类B及其相应的标头:

A.H

#ifndef CLASS_A
#define CLASS_A
/* forward declare A */
class A;
/* includes */
#include "b.h"
/* define class A */
class A {
public:
A() : p_b(nullptr) {}
B *p_b;
};
#endif

B.H

#ifndef CLASS_B
#define CLASS_B
/* forward declare B */
class B;
/* includes */
#include "a.h"
/* define class B */
class B {
public:
B() : m_a() {}
A m_a;
};
#endif

这不起作用:

为了将 A 的实现编译成一个对象文件,我首先包含 a.h,它向前声明 A,然后包含 b.h,然后声明并定义 B。但是当定义 B 时,它不知道 A 的大小,因此不能将 A 的对象声明为 B 的成员。

然而,A 不需要知道 B 的大小,因为它只有一个指向 B 的指针,并且可以在 B 定义之前完全定义。因此,在将 B 用作成员之前,可以完全知道它的大小,并且完整的声明应该没问题。

常识告诉 a.c 文件应该始终如下所示:

#include "a.h"
[...]

我可以通过在 a.c 中包含 a.h 之前的 b.h 来实际解决问题吗?这是否违反了将实现文件的第一行包含在其标头中的某种神圣惯例?

您正在以向后的方式使用前向声明。代码应该看起来更像这样:

A.H

#ifndef CLASS_A
#define CLASS_A
/* forward declare B */
class B;
/* define class A */
class A {
public:
A() : p_b(nullptr) {}
B *p_b;
};
#endif

B.H

#ifndef CLASS_B
#define CLASS_B
#include "a.h"
/* define class B */
class B {
public:
B() : m_a() {}
A m_a;
};
#endif

a.h不需要知道B到底是什么,因为A包含一个B*指针,而不是一个B对象。因此,a.h根本不应该使用#include "b.h",而应该是前向声明B

b.h确实需要知道A实际上是什么,因为B包含一个A对象,而不是一个A*指针。所以b.h应该使用#include "a.h",它在定义A之前已经向前声明了B,然后b.h完成定义B

然后,a.c可以使用#include "a.h"引入A声明,以便完成实现的定义,并且仅当A的方法需要访问B的成员时,它才能使用#include "b.h"

让我们看看编译器在预处理器完成其操作后看到的内容:

/* forward declare A */
class A;
/* includes */
/* forward declare B */
class B;
/* includes */
/* define class B */
class B {
public:
B() : m_a() {}
A m_a;
};
/* define class A */
class A {
public:
A() : p_b(nullptr) {}
B *p_b;
};

如您所见,B的类定义在完全定义之前A,因此您的程序格式不正确。

在这里,你只需要一个前向声明(B),它应该在A.h中A的定义之前:

#ifndef CLASS_A
#define CLASS_A
// Forward declare B so that B* p_b is legal
class B;
// Note that B.h is *not* included here
class A {
public:
A() : p_b(nullptr) {}
B *p_b;
};
#endif

在这里,您可以通过向前声明B而不是包含B的完整定义来打破循环包含循环。 大概在 A 中.cpp您将#includeB的完整定义,以便您可以使用其成员。

由于每个类都依赖于另一个类,因此这两个类应在同一头文件(和同一命名空间)中定义。 如果由于某种原因它们必须位于不同的头文件中,这将起作用。

#ifndef CLASS_A
#define CLASS_A
class B;
class A {
public:
A() : p_b(nullptr) {}
B *p_b;
};
#endif

B.h

#ifndef CLASS_B
#define CLASS_B
#include "a.h"
class B {
public:
B() : m_a() {}
A m_a;
};
#endif