C++ class forward declaration

2019-01-17 08:57发布

问题:

When I try to compile this code i get:

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' 

some part of my code:

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;};       
};

I dont really know what to do, I searched for the solution but I couldnt find anything simmilar to my problem... Actually, i have more classes with parent "tile" and It was ok before... Thanx for any help.

EDIT:

I decided to change all returned types to pointers to avoid memory leaks, but now I got:

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"

Its only in base class, everything else is ok... Every function in tile class which return *tile has this error...

Some code:

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() {};
};

回答1:

In order for new T to compile, T must be a complete type. In your case, when you say new tile_tree_apple inside the definition of tile_tree::tick, tile_tree_apple is incomplete (it has been forward declared, but its definition is later in your file). Try moving the inline definitions of your functions to a separate source file, or at least move them after the class definitions.

Something like:

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() {...}

When you write your code this way, all references to A and B in these methods are guaranteed to refer to complete types, since there are no more forward references!



回答2:

Use forward declaration when possible.

Suppose you want to define a new class B that uses objects of class A.

  1. B only uses references or pointers to A. Use forward declaration then you don't need to include <A.h>. This will in turn speed a little bit the compilation.

    class A ;
    
    class B 
    {
      private:
        A* fPtrA ;
      public:
        void mymethod(const& A) const ;
    } ;
    
  2. B derives from A or B explicitely (or implicitely) uses objects of class A. You then need to include <A.h>

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


回答3:

The forward declaration is an "incomplete type", the only thing you can do with such a type is instantiate a pointer to it, or reference it in a function declaration (i.e. and argument or return type in a function prototype). In line 52 in your code, you are attempting to instantiate an object.

At that point the compiler has no knowledge of the object's size nor its constructor, so cannot instantiate an object.



回答4:

I had this:

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;
}

...

Got the same grief as in the original question. Only solved by moving the definition of paulzSprite to after that of spriteFrame. Shouldn't the compiler be smarter than this (VC++, VS 11 Beta)?

And btw, I wholeheartedly agree with Clifford's remark above "Pointers don't cause memory leaks, poor coding causes memory leaks". IMHO this is true of many other new "smart coding" features, which should not become a substitute for understanding what you are actually asking the computer to do.



回答5:

The problem is that tick() needs to know the definition of tile_tree_apple, but all it has is a forward declaration of it. You should separate the declarations and definitions like so:

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;
}

Except you have a major problem: you’re allocating memory (with new), then copying the allocated object and returning the copy. This is called a memory leak, because there’s no way for your program to free the memory it uses. Not only that, but you’re copying a tile_tree into a tile, which discards the information that makes a tile_tree different from a tile; this is called slicing.

What you want is to return a pointer to a new tile, and make sure you call delete at some point to free the memory:

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

Even better would be to return a smart pointer that will handle the memory management for you:

#include <memory>

std::shared_ptr<tile> tile_tree::tick() {
     if (rand() % 20 == 0)
         return std::make_shared<tile_tree_apple>();
}


回答6:

class tile_tree_apple should be defined in a separate .h file.

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;};        
};

another thing: returning a tile and not a tile reference is not a good idea, unless a tile is a primitive or very "small" type.



回答7:

To do anything other than declare a pointer to an object, you need the full definition.

The best solution is to move the implementation in a separate file.

If you must keep this in a header, move the definition after both declarations:

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;};       

Important

You have memory leaks:

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

will create an object on the heap, which you can't destroy afterwards, unless you do some ugly hacking. Also, your object will be sliced. Don't do this, return a pointer.



回答8:

To perform *new tile_tree_apple the constructor of tile_tree_apple should be called, but in this place compiler knows nothing about tile_tree_apple, so it can't use the constructor.

If you put

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

in separate cpp file which has the definition of class tile_tree_apple or includes the header file which has the definition everything will work fine.



回答9:

I'm a CPP noob, but don't you have to specify the inheritance in the forward declaration?

class tile_tree_apple : public tile;