前向声明?,包括警卫?,或其他什么

Forward Declaration?, Include guard?, or something else?

本文关键字:其他 什么 包括警 声明      更新时间:2023-10-16

我有一个名为 Token 的父类

有两个子类,ErrorToken和EndToken这些类中的每一个都需要能够创建另一个类的对象,并通过函数调用将其返回。 每个都有自己单独的标头类。

因此,ErrorToken 需要能够创建一个新的 EndToken 对象并返回

它,而 EndToken 需要能够创建一个新的 ErrorToken 对象并返回它。

成功做到这一点的最佳方法是什么? 我希望它尽可能与交叉编译器兼容,所以我不想使用一次编译指示。 (但这本质上是我正在寻找的)。

理想情况下,我希望能够做这样的事情...

#ifndef ErrorToken
#include "ErrorToken.h"
#endif

但这似乎永远不会奏效(我的猜测是错误的? 有人可以帮助我理解为什么吗?

我对前向声明的理解是它仅适用于函数签名和指针(这是正确的吗?),所以我认为这不适用于我的情况,因为我需要它能够运行构造函数......还是编译器只需要知道构造函数在那一刻退出?

好吧,使用前向声明。正如你所说,那里有数百万种解释,现在有数百万零一种:

ErrorToken.h:

#ifndef H_ERROR_TOKEN
#define H_ERROR_TOKEN
#include "Token.h"
class EndToken;
class ErrorToken : public Token
{
public:
    EndToken makeEndToken();
};
#endif

EndToken.h:

#ifndef H_END_TOKEN
#define H_END_TOKEN
#include "Token.h"
class ErrorToken;
class EndToken : public Token
{
public:
    ErrorToken makeErrorToken();
};
#endif

在每个实现文件中,您现在可以愉快地包含两个标头:

#include "ErrorToken.h"
#include "EndToken.h"
ErrorToken EndToken::makeErrorToken()
{
    return ErrorToken();   // example
}
EndToken ErrorToken::makeEndToken()
{
    return EndToken();
}

正如@James Kanze指出的那样,您可能会对C++的工作方式感到困惑。以下代码可能更符合您期望从 Java 获得的行为类型,并且在多态设计方式中更有意义:

class Token { virtual ~Token()  {} };
class ErrorToken : public Token
{
    std::unique_ptr<Token> makeEndToken();
};
class EndToken : public Token
{
    std::unique_ptr<Token> makeErrorToken();
};
std::unique_ptr<Token> EndToken::makeErrorToken()
{
    return { new ErrorToken; }
}
std::unique_ptr<Token> ErrorToken::makeEndToken()
{
    return { new EndToken; }
}

由于您仅通过基指针处理对象,因此标头不需要了解有关其他派生类的任何信息。(我让你们将代码细分为文件;每个块都进入一个单独的文件。

为什么你的

#ifndef ErrorToken

错?首先,将其放在头文件中,其次确保某些内容确实定义了ErrorToken


包括守卫和转发声明可能会让你摆脱这个洞:

//"ErrorToken.h"
#ifndef ERROR_TOKEN_INCLUDED
#define ERROR_TOKEN_INCLUDED
class EndToken;
class ErrorToken
{
public:
    EndToken DoSomething();
};
#endif
//"EndToken.h"
#ifndef END_TOKEN_INCLUDED
#define END_TOKEN_INCLUDED
class ErrorToken;
class EndToken
{
public:
    ErrorToken DoSomething();
};
#endif

之前已经有几次关于循环依赖关系的讨论:这里,这里和这里
然后,您可以在 cpp 文件中完成大部分工作,其中包含另一个标头,以便它知道完整的类定义,而不仅仅是前向声明,并且可以使用它。
不过,先退后一步,问道:"他们真的需要了解彼此吗?