C++类的前向声明

30

当我尝试编译此代码时,我会遇到以下错误:

52 C:\Dev-Cpp\Projektyyy\strategy\Tiles.h invalid use of undefined type `struct tile_tree_apple' 
46 C:\Dev-Cpp\Projektyyy\strategy\Tiles.h forward declaration of `struct tile_tree_apple' 

我的一部分代码:

class tile_tree_apple;

class tile_tree : public tile
{
      public:
          tile onDestroy() {return *new tile_grass;};
          tile tick() {if (rand()%20==0) return *new tile_tree_apple;};
          void onCreate() {health=rand()%5+4; type=TILET_TREE;};        
};

class tile_tree_apple : public tile
{
      public:
          tile onDestroy() {return *new tile_grass;};
          tile tick() {if (rand()%20==0) return *new tile_tree;};
          void onCreate() {health=rand()%5+4; type=TILET_TREE_APPLE;}; 
          tile onUse() {return *new tile_tree;};       
};

我不太清楚该怎么办,我搜索了解决方案,但我找不到与我的问题类似的东西...实际上,我有更多父级为“tile”的类,以前都没问题...

编辑:

我决定将所有返回类型更改为指针以避免内存泄漏,但现在我遇到了:

27 C:\Dev-Cpp\Projektyyy\strategy\Tiles.h ISO C++ forbids declaration of `tile' with no type 
27 C:\Dev-Cpp\Projektyyy\strategy\Tiles.h expected `;' before "tick"

只有在基类中出现了这个问题,其他一切都正常... 在tile类中每个返回 *tile 的函数都有这个错误...

一些代码:

class tile
{
      public:
          double health;
          tile_type type;
          *tile takeDamage(int ammount) {return this;};
          *tile onDestroy() {return this;};
          *tile onUse() {return this;};
          *tile tick() {return this};
          virtual void onCreate() {};
};
8个回答

34

尽可能使用前置声明。

假设你想定义一个新的类 B,它使用类A的对象。

  1. B仅使用A的引用或指针。在这种情况下,使用前置声明,就不需要包含<A.h>头文件。这将稍微加快编译速度。

class A ;

class B 
{
  private:
    A* fPtrA ;
  public:
    void mymethod(const& A) const ;
} ;
  • B继承自A或者B明确(或隐式)使用了A类的对象。你需要包含<A.h>

  • #include <A.h>
    
    class B : public A 
    {
    };
    
    class C 
    {
      private:
        A fA ;
      public:
        void mymethod(A par) ;   
    }
    

    应该是:void mymethod(const A &) const。 - KIIV

    24
    为了使new T编译通过,T必须是一个完整的类型。在您的情况下,当您在tile_tree::tick的定义中说new tile_tree_apple时,tile_tree_apple是不完整的(它已被前向声明,但其定义稍后在您的文件中)。尝试将函数的内联定义移动到单独的源文件中,或者至少将它们移动到类定义之后。
    类似于:
    class A
    {
        void f1();
        void f2();
    };
    class B
    {
       void f3();
       void f4();
    };
    
    inline void A::f1() {...}
    inline void A::f2() {...}
    inline void B::f3() {...}
    inline void B::f4() {...}
    

    当您以这种方式编写代码时,这些方法中对A和B的所有引用都保证是完整类型的引用,因为不再有前向引用!

    1
    @KerrekSB:如果我没记错的话,它应该放在声明或定义中,但无论哪一个都无所谓。 - Armen Tsirunyan
    3
    它只需要放在定义中,放在声明中没有任何影响。 - ildjarn
    成功了!给我5分钟,我接受它 :D(由于网站限制)。而且我必须将所有东西都改为指针,讨厌内存泄漏 :D ... Java已经为我完成了所有指针操作 xD。 - noisy cat
    5
    使用智能指针(smart pointers)可以避免内存泄漏。 - Armen Tsirunyan
    14
    指针本身不会导致内存泄漏,糟糕的编码才会导致内存泄漏。 - Clifford
    显示剩余3条评论

    9
    前向声明是一种“不完整类型”,你只能实例化一个指向它的指针,或在函数声明中引用它(例如作为函数原型中的参数或返回类型)。在你的代码的第52行,你正在尝试实例化一个对象。此时编译器不知道对象的大小和构造函数,因此不能实例化一个对象。

    2
    有许多其他的事情可以使用不完整类型完成。 - Kerrek SB
    1
    如何在不实例化对象的情况下实例化指针? - Luchian Grigore
    @Luchian:在这种情况下:tile_tree_apple* tta_ptr; 实例化了一个 tile_tree_apple* 类型的指针,尽管它当然不指向有效的对象。关键是你可能会将这样的指针作为类的成员,在构造函数中实例化对象,但无论哪种方式,在代码中 完整 类型可见的时候。 - Clifford
    1
    @Kerrek:也许有一些,但也许与这个讨论无关。不过,如果您能详细说明可能会很有用。话虽如此,从您的评论时间来看,我在上次编辑中可能已经涵盖了它们。 - Clifford

    6

    我有这个:

    class paulzSprite;
    ...
    
    struct spriteFrame
    {
        spriteFrame(int, int, paulzSprite*, int, int);
        paulzSprite* pSprite; //points to the sprite class this struct frames
        static paulzSprite* pErase; //pointer to blanking sprite
        int x, y;
        int Xmin, Xmax, Ymin, Ymax; //limits, leave these to individual child classes, according to bitmap size
        bool move(int, int);
        bool DrawAt(int, int);
        bool dead;
    };
    
    spriteFrame::spriteFrame(int initx, int inity, paulzSprite* pSpr, int winWidth, int winHeight)
    {
        x = initx;
        y= inity;
        pSprite = pSpr;
        Xmin = Ymin = 0;
        Xmax = winWidth - pSpr->width;
        Ymax = winHeight - pSpr->height;
        dead = false;
    }
    

    我遇到了与原问题相同的困扰。只有将paulzSprite的定义移动到spriteFrame之后才解决了这个问题。编译器难道不能更聪明一些吗?(VC++, VS 11 Beta)

    顺便说一句,我完全同意Clifford上面的评论:“指针不会导致内存泄漏,糟糕的编码会导致内存泄漏”。在我看来,这也适用于许多其他新的“智能编码”功能,它们不应该成为理解您实际要求计算机执行的操作的替代品。


    5
    问题在于tick()需要知道tile_tree_apple的定义,但它只有对其前置声明。您应该像下面这样将声明和定义分开:

    tile_tree.h

    #ifndef TILE_TREE_H
    #define TILE_TREE_H
    #include "tile.h"
    
    class tile_tree : public tile
    {
    public:
        tile onDestroy();
        tile tick();
        void onCreate();
    };
    
    #endif
    

    tile_tree.cpp:

    tile tile_tree::onDestroy() {
        return *new tile_grass;
    }
    
    tile tile_tree::tick() {
         if (rand() % 20 == 0)
             return *new tile_tree_apple;
    }
    
    void tile_tree::onCreate() {
        health = rand() % 5 + 4;
        type = TILET_TREE;
    }
    

    除非你有一个重大问题:你正在分配内存(使用new),然后复制已分配的对象并返回副本。这被称为内存泄漏,因为程序无法释放其使用的内存。不仅如此,而且你正在将tile_tree复制到tile中,这会丢弃使tile_treetile不同的信息;这被称为切片
    你想要的是返回指向新tile的指针,并确保在某个时候调用delete以释放内存:
    tile* tile_tree::tick() {
         if (rand() % 20 == 0)
             return new tile_tree_apple;
    }
    

    更好的做法是返回一个智能指针,它将帮助您处理内存管理:
    #include <memory>
    
    std::shared_ptr<tile> tile_tree::tick() {
         if (rand() % 20 == 0)
             return std::make_shared<tile_tree_apple>();
    }
    

    3

    应该在单独的.h文件中定义类tile_tree_apple。

    tta.h:
    #include "tile.h"
    
    class tile_tree_apple : public tile
    {
          public:
              tile onDestroy() {return *new tile_grass;};
              tile tick() {if (rand()%20==0) return *new tile_tree;};
              void onCreate() {health=rand()%5+4; type=TILET_TREE_APPLE;}; 
              tile onUse() {return *new tile_tree;};       
    };
    
    file tt.h
    #include "tile.h"
    
    class tile_tree : public tile
    {
          public:
              tile onDestroy() {return *new tile_grass;};
              tile tick() {if (rand()%20==0) return *new tile_tree_apple;};
              void onCreate() {health=rand()%5+4; type=TILET_TREE;};        
    };
    

    另外一件事:除非一个Tile是一个基本类型或者非常“小”的类型,否则返回一个Tile而不是一个Tile引用不是一个好主意。


    tile_tree_apple需要通过include了解tile_tree,反之亦然吗? - Bren

    1

    除了声明对象指针之外,如果要执行其他操作,则需要完整的定义。

    最好的解决方案是将实现移动到单独的文件中。

    如果您必须将其保留在头文件中,请在两个声明后面移动定义:

    class tile_tree_apple;
    
    class tile_tree : public tile
    {
      public:
          tile onDestroy();
          tile tick();
          void onCreate();        
    };
    
    class tile_tree_apple : public tile
    {
      public:
          tile onDestroy();
          tile tick();
          void onCreate(); 
          tile onUse();       
    };
    
    tile tile_tree::onDestroy() {return *new tile_grass;};
    tile tile_tree::tick() {if (rand()%20==0) return *new tile_tree_apple;};
    void tile_tree::onCreate() {health=rand()%5+4; type=TILET_TREE;};        
    
    tile tile_tree_apple::onDestroy() {return *new tile_grass;};
    tile tile_tree_apple::tick() {if (rand()%20==0) return *new tile_tree;};
    void tile_tree_apple::onCreate() {health=rand()%5+4; type=TILET_TREE_APPLE;}; 
    tile tile_tree_apple::onUse() {return *new tile_tree;};       
    

    重要提示

    您的程序存在内存泄漏问题:

    tile tile_tree::onDestroy() {return *new tile_grass;};
    

    将在堆上创建一个对象,除非进行一些丑陋的黑客攻击,否则您无法在之后销毁它。此外,您的对象将被切片。不要这样做,返回指针。


    这是不正确的。请查阅标准。例如,如果T是一个不完整的类型,您可以声明(但不能定义)一个接受T的函数。您还可以声明对T的引用。 - Armen Tsirunyan
    2
    这个链接应该很有用。 - Alok Save
    函数的返回类型也可能是不完整的。 - Kerrek SB

    0

    要执行* new tile_tree_apple,需要调用tile_tree_apple的构造函数,但在这个地方编译器并不知道tile_tree_apple,所以它无法使用构造函数。

    如果您放置

    tile tile_tree::tick() {if (rand()%20==0) return *new tile_tree_apple;};
    

    在单独的cpp文件中,该文件包含类tile_tree_apple的定义或包含该定义的头文件,一切都将正常工作。


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