Предварительное объявление класса C++

Когда я пытаюсь скомпилировать этот код, я получаю:

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

Я действительно не знаю, что делать, я искал решение, но не смог найти ничего похожего на мою проблему... На самом деле, у меня больше классов с родительской плиткой, и раньше все было в порядке...

РЕДАКТИРОВАТЬ:

Я решил изменить все возвращаемые типы на указатели, чтобы избежать утечек памяти, но теперь я получил:

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, имеет эту ошибку...

Некоторый код:

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

person noisy cat    schedule 02.02.2012    source источник


Ответы (8)


Чтобы new T скомпилировался, T должен быть полным типом. В вашем случае, когда вы говорите new tile_tree_apple внутри определения tile_tree::tick, 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 в этих методах гарантированно относятся к полным типам, поскольку прямых ссылок больше нет!

person Armen Tsirunyan    schedule 02.02.2012
comment
Разве inline тоже не должно быть в определении класса? Я никогда не был слишком уверен в этом... - person Kerrek SB; 03.02.2012
comment
@KerrekSB: AFAIR, это должно идти либо к объявлению, либо к определению, но не имеет значения, к какому. - person Armen Tsirunyan; 03.02.2012
comment
@KerrekSB: это нужно только для определения; включение его в декларацию не имеет никакого эффекта. - person ildjarn; 03.02.2012
comment
РАБОТАЛ! Дайте мне 5 минут, и я принимаю это: D (из-за ограничений сайта). И я должен изменить все на указатели, ненавижу утечки памяти: D ... Java делал все с указателями для меня xD - person noisy cat; 03.02.2012
comment
@kittyPL: используйте умные указатели, чтобы избежать утечек памяти. - person Armen Tsirunyan; 03.02.2012
comment
Я был бы очень рад, если бы вы также могли помочь мне с вопросом в РЕДАКТИРОВАТЬ :) - person noisy cat; 03.02.2012
comment
@kittyPL: замените *tile на tile* и не игнорируйте мой комментарий об умных указателях. Они действительно полезны. - person Armen Tsirunyan; 03.02.2012
comment
@kittyPL: указатели не вызывают утечек памяти, плохое кодирование вызывает утечки памяти. - person Clifford; 03.02.2012

Используйте предварительное объявление, когда это возможно.

Предположим, вы хотите определить новый класс B, который использует объекты класса A.

  1. B использует только ссылки или указатели на A. Используйте предварительное объявление, тогда вам не нужно включать <A.h>. Это, в свою очередь, немного ускорит компиляцию.

    class A ;
    
    class B 
    {
      private:
        A* fPtrA ;
      public:
        void mymethod(const& A) const ;
    } ;
    
  2. B происходит от A или B явно (или неявно) использует объекты класса A. Затем вам нужно включить <A.h>

    #include <A.h>
    
    class B : public A 
    {
    };
    
    class C 
    {
      private:
        A fA ;
      public:
        void mymethod(A par) ;   
    }
    
person kumaran    schedule 07.11.2012
comment
Это должно быть: void mymethod(const A &) const. - person KIIV; 09.06.2016

Предварительное объявление — это «неполный тип», единственное, что вы можете сделать с таким типом, — создать экземпляр указателя на него или сослаться на него в функции объявление (т. е. и тип аргумента или возвращаемого значения в прототипе функции). В строке 52 кода вы пытаетесь создать экземпляр объекта.

В этот момент компилятор не знает ни размера объекта, ни его конструктора, поэтому не может создать экземпляр объекта.

person Clifford    schedule 02.02.2012
comment
Есть много других вещей, которые вы можете сделать с неполным типом. - person Kerrek SB; 03.02.2012
comment
Как создать экземпляр указателя без создания экземпляра объекта? - person Luchian Grigore; 03.02.2012
comment
@Luchian: В этом случае: tile_tree_apple* tta_ptr ; Создает экземпляр указателя типа tile_tree_apple*, хотя, конечно, он не указывает на допустимый объект. Дело в том, что у вас может быть такой указатель как член класса, который позже создает экземпляр объекта, например, в конструкторе, но в любом случае в той точке кода, где виден тип complete. - person Clifford; 03.02.2012
comment
@Kerrek: Возможно, но, возможно, ничего из того, что имеет отношение к этому обсуждению. Было бы полезно, хотя, возможно, если бы вы могли уточнить. Тем не менее, глядя на время вашего комментария, я, возможно, уже рассмотрел их в последнем редактировании. - person Clifford; 03.02.2012

У меня было это:

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)?

И кстати, я полностью согласен с замечанием Клиффорда выше: «Указатели не вызывают утечек памяти, плохое кодирование вызывает утечки памяти». ИМХО, это верно и для многих других новых функций «умного кодирования», которые не должны заменять понимание того, что вы на самом деле просите компьютер сделать.

person Paul Pignon    schedule 09.12.2012

Проблема в том, что 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_tree от tile; это называется нарезка.

Что вы хотите, так это вернуть указатель на новый 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>();
}
person Jon Purdy    schedule 02.02.2012

класс tile_tree_apple должен быть определен в отдельном файле .h.

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

еще одна вещь: возвращать тайл, а не ссылку на тайл, не очень хорошая идея, если только тайл не является примитивным или очень «маленьким» типом.

person vulkanino    schedule 02.02.2012
comment
Разве tile_tree_apple не нужно знать о tile_tree через включение и наоборот? - person Bren; 11.03.2016

Чтобы сделать что-либо, кроме объявления указателя на объект, вам нужно полное определение.

Лучшее решение – перенести реализацию в отдельный файл.

Если вы должны сохранить это в заголовке, переместите определение после обоих объявлений:

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

создаст объект в куче, который вы не сможете уничтожить впоследствии, если только вы не сделаете какой-нибудь уродливый взлом. Кроме того, ваш объект будет нарезан. Не делайте этого, верните указатель.

person Luchian Grigore    schedule 02.02.2012
comment
Это неправда. Посмотрите стандарт. Например, вы можете объявить (но не определить) функцию, которая принимает T, если T — неполный тип. Вы также можете объявить ссылки на T. - person Armen Tsirunyan; 03.02.2012
comment
Это должно быть полезно, - person Alok Save; 03.02.2012
comment
Тип возвращаемого значения функции также может быть неполным. - person Kerrek SB; 03.02.2012

Для выполнения *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 или включает заголовочный файл, который имеет определение, все будет работать нормально.

person Seagull    schedule 02.02.2012