无效使用不完整的类型/前向声明

17

我尝试查看Stackoverflow和Google上列出的类似问题,但它们主要涉及模板,这不适用于我的情况。我正在使用Debian Testing 64位上的GCC 4.4.5。
因此,我有两个类 - CEntity:

#ifndef CENTITY_H_INCLUDED
#define CENTITY_H_INCLUDED

#include "global_includes.h"

// game
#include "CAnimation.h"
#include "Vars.h"
#include "vector2f.h"
#include "Utils.h"

class CAnimation;

class CEntity
{
public:
    CEntity();
    virtual ~CEntity();

    void _update(Uint32 dt);

    void updateAnimation(Uint32 dt);

    void addAnimation(const std::string& name, CAnimation* anim);
    void addAnimation(const std::string& name, const CAnimation& anim);
    void removeAnimation(const std::string& name);
    void clearAnimations();

    bool setAnimation(const std::string& name);

    SDL_Surface* getImage() const;

    const vector2f& getPos() const;
    const vector2f& getLastPos() const;
    F getX() const;
    F getY() const;
    F getLastX() const;
    F getLastY() const;
    SDL_Rect* getHitbox() const;
    SDL_Rect* getRect() const;

    F getXSpeed() const;
    F getYSpeed() const;

    void setPos(const vector2f& pos);
    void setPos(F x, F y);
    void setPos(F n);
    void setX(F x);
    void setY(F y);

    void setHitboxSize(int w, int h);
    void setHitboxSize(SDL_Rect* rect);
    void setHitboxWidth(int w);
    void setHitboxHeight(int h);

    void setSpeed(F xSpeed, F ySpeed);
    void setXSpeed(F xSpeed);
    void setYSpeed(F ySpeed);

    void stop();
    void stopX();
    void stopY();

    void affectByGravity(bool affect);

    void translate(const vector2f& offset);
    void translate(F x, F y);

    bool collide(CEntity& s);
    bool collide(CEntity* s);

protected:
    CAnimation* mCurrentAnimation;
    SDL_Surface* mImage;

    vector2f mPos;
    vector2f mLastPos;
    SDL_Rect* mHitbox; // used for collisions
    SDL_Rect* mRect; // used only for blitting

    F mXSpeed;
    F mYSpeed;

    bool mAffByGrav;

    int mHOffset;
    int mVOffset;

private:
    std::map<std::string, CAnimation*> mAnims;
};

#endif // CENTITY_H_INCLUDED

以及从CEntity继承的CPlayerChar:

#ifndef CPLAYERCHAR_H_INCLUDED
#define CPLAYERCHAR_H_INCLUDED

#include "global_includes.h"

// game
#include "CEntity.h"

class CEntity;

class CPlayerChar : public CEntity
{
public:
    CPlayerChar();
    virtual ~CPlayerChar();

    virtual void update(Uint32 dt) = 0;

    virtual void runLeft() = 0;
    virtual void runRight() = 0;
    virtual void stopRunLeft() = 0;
    virtual void stopRunRight() = 0;

    virtual void attack() = 0;
    virtual void stopAttack() = 0;

    virtual void attack2() = 0;
    virtual void stopAttack2() = 0;

    virtual void ground() = 0;
    virtual void midair() = 0;

    void jump();
    void stopJump();

protected:
    // looking right?
    bool mRight;

    bool mJumping;
    bool mOnGround;
    bool mGrounded;
};

#endif // CPLAYERCHAR_H_INCLUDED

当我尝试编译时,GCC报告了以下错误:

CPlayerChar.h:12: error: invalid use of incomplete type ‘struct CEntity’
CPlayerChar.h:9: error: forward declaration of ‘struct CEntity’

我先尝试在CPlayerChar.h的第9行没有前置声明'class CEntity;'的情况下运行它,但这样会抛出以下异常。

CPlayerChar.h:12: error: expected class-name before ‘{’ token

因此,必须存在前向声明。另外,CEntity明显是一个类,而不是一个结构体。


1
在C++中,类和结构体几乎是相同的;不要被错误信息所困扰。 - Mark Ransom
1
我猜测 CEntity 类的定义在文件 CEntity.h 中,如果不是,那就是你的问题。 - Mark Ransom
1
你的头文件存在一些循环包含。头文件只有在明确需要时才应该包含其他头文件。如果可以使用前向声明,则应优先考虑使用它。 - Martin York
3个回答

12

你的头文件存在循环包含。
但是如果没有所有的头文件,我们将无法修复它。

我会从这里开始。

#include "CAnimation.h"

看着你的头文件,你其实不需要这个。你只会通过引用或指针使用 CAnimation,所以你已经有了足够的前向声明。将 include 移到源文件中(即移出头文件)。

下一个我会看的地方是:

#include "global_includes.h"

全局包含在头文件中的内容最好非常简单。它们应该只包含简单类型,不应包含任何其他头文件(除非它们同样简单)。任何复杂的内容都会导致循环依赖问题。

一般规则

头文件应仅包含其绝对需要的头文件。否则,这些头文件应从源文件中包含。只有在以下情况下才绝对需要包含头文件:它定义了一个作为父类使用的类,你拥有该类的成员对象或者你使用该类的参数对象。

我使用术语“对象”以区分引用或指针。如果你正在使用引用或指针,则无需包含头文件。您只需要进行前向声明。


关于循环依赖,如果他有#ifndef CANIMATION_H_INCLUDED,那么问题不应该被解决吗? - antogerva
@antogerva 这有所帮助,但还不够。您还需要前向声明类型。此外,只包括您特定需要的头文件并前向声明其他类型。 - Martin York

6
你可能在包含文件中出现了循环,导致CPlayerChar不知道CEntity到底是什么,它只知道它存在,但不知道它是什么。
如果你移除"class CEntity"声明,你会发现GCC会报错,提示CEntity不存在。
你必须检查CEntity所包含的内容中没有包含CPlayerChar。

谢谢,你最后那行代码应该就是了。CEntity 包含另一个文件,其中包括 CEntity 和 CPlayerChar。现在我只需要找到一个解决方法。 - rivon

5
你需要确保在定义类CPlayerChar之前,类CEntity的完整定义可见。(请检查你的inclusions)
这是因为你只能从完全定义的类继承,而不是仅从前向声明的类继承。
唯一可以使用前向声明代替完整定义的情况是当你创建一个指向该类型的指针或引用时,但只有当你从未访问其任何成员时才可以这样做,或者(感谢@Alf)在声明具有不完整返回类型的函数时。

1
前向声明比您所表述的要更强大一些。例如,如果T是不完全类型,则声明函数T foo()是完全可以的。为了调用它,您需要完成T(除非T是特殊类型void,其在形式上是一个永远无法完成的不完全类型),但这是另一个问题。 :-) - Cheers and hth. - Alf
@Alf:谢谢!这很有用。那么函数参数呢?它们可以不完整吗? - Kerrek SB

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