С++ #include охранники

решаемые

Что действительно помогло мне, так это то, что я мог # включать заголовки в .cpp файл, вызывая переопределенную ошибку.


Я новичок в С++, но у меня есть некоторый опыт программирования на С# и Java, поэтому я могу пропустить что-то основное, уникальное для С++.

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

У меня есть три класса, GameEvents, Physics и GameObject. У меня есть заголовки для каждого из них. GameEvents имеет одну физику и список GameObjects. Физика имеет список GameObjects.

То, что я пытаюсь достичь, - это то, что я хочу, чтобы GameObject мог иметь доступ к физическому объекту или иметь его.

Если я просто #include "Physics.h" в GameObject, я получаю "ошибка C2111:" ClassXXX ": переопределение типа" класс ", которое я понимаю. И вот где я думал, что # include-guards поможет, поэтому я добавил включенную защиту для моего Phys.h, так как заголовок, который я хочу включить дважды.

Вот как выглядит

#ifndef PHYSICS_H
#define PHYSICS_H

#include "GameObject.h"
#include <list>


class Physics
{
private:
    double gravity;
    list<GameObject*> objects;
    list<GameObject*>::iterator i;
public:
    Physics(void);
    void ApplyPhysics(GameObject*);
    void UpdatePhysics(int);
    bool RectangleIntersect(SDL_Rect, SDL_Rect);
    Vector2X CheckCollisions(Vector2X, GameObject*);
};

#endif // PHYSICS_H

Но если я # включил "Physics.h" в свой GameObject.h сейчас вот так:

#include "Texture2D.h"
#include "Vector2X.h"
#include <SDL.h>
#include "Physics.h"

class GameObject
{
private:
    SDL_Rect collisionBox;
public:
    Texture2D texture;
    Vector2X position;
    double gravityForce;
    int weight;
    bool isOnGround;
    GameObject(void);
    GameObject(Texture2D, Vector2X, int);
    void UpdateObject(int);
    void Draw(SDL_Surface*);
    void SetPosition(Vector2X);
    SDL_Rect GetCollisionBox();
};

Я получаю несколько проблем, которые не понимают, почему они появляются. Если я не #include "Physics.h" , мой код работает нормально.

Я очень благодарен за любую помощь.

Ответ 1

Препроцессор - это программа, которая принимает вашу программу, вносит некоторые изменения (например, включают файлы (#include), расширение макросов (#define) и в основном все, что начинается с #), и дает "чистый" результат компилятору.

Препроцессор работает так, когда видит #include:

Когда вы пишете:

#include "some_file"

Содержимое some_file почти буквально получает копию, вставленную в файл, включая его. Теперь, если у вас есть:

a.h:
class A { int a; };

и

b.h:
#include "a.h"
class B { int b; };

и

main.cpp:
#include "a.h"
#include "b.h"

Вы получаете:

main.cpp:
class A { int a; };  // From #include "a.h"
class A { int a; };  // From #include "b.h"
class B { int b; };  // From #include "b.h"

Теперь вы можете увидеть, как переопределяется A.

Когда вы пишете охранники, они становятся такими:

a.h:
#ifndef A_H
#define A_H
class A { int a; };
#endif

b.h:
#ifndef B_H
#define B_H
#include "a.h"
class B { int b; };
#endif

Итак, посмотрим, как будет расширяться #include в main (это точно так же, как в предыдущем случае: copy-paste)

main.cpp:
// From #include "a.h"
#ifndef A_H
#define A_H
class A { int a; };
#endif
// From #include "b.h"
#ifndef B_H
#define B_H
#ifndef A_H          // From
#define A_H          // #include "a.h"
class A { int a; };  // inside
#endif               // "b.h"
class B { int b; };
#endif

Теперь перейдем к препроцессору и посмотрим, что из этого получается "настоящий" код. Я пойду по строчке:

// From #include "a.h"

Комментарий. Игнорировать! Далее:

#ifndef A_H

Определен ли A_H? Нет! Затем продолжайте:

#define A_H

Ok теперь A_H. Далее:

class A { int a; };

Это не что-то для препроцессора, так что просто оставьте это. Далее:

#endif

Предыдущая if завершена здесь. Далее:

// From #include "b.h"

Комментарий. Игнорировать! Далее:

#ifndef B_H

Определен ли B_H? Нет! Затем продолжайте:

#define B_H

Ok теперь B_H. Далее:

#ifndef A_H          // From

Определен ли A_H? ДА! Затем игнорируйте до соответствующего #endif:

#define A_H          // #include "a.h"

Игнорировать

class A { int a; };  // inside

Игнорировать

#endif               // "b.h"

Предыдущая if завершена здесь. Далее:

class B { int b; };

Это не что-то для препроцессора, так что просто оставьте это. Далее:

#endif

Предыдущая if завершена здесь.

То есть, после выполнения препроцессора с файлом, это то, что видит компилятор:

main.cpp
class A { int a; };
class B { int b; };

Итак, как вы можете видеть, все, что может получить #include d в том же файле дважды, прямо или косвенно, нужно защищать. Поскольку файлы .h всегда с большой вероятностью будут включены дважды, хорошо, если вы охраняете ВСЕ ваши .h файлы.

P.S. Обратите внимание, что у вас также есть круговой #include s. Представьте, что препроцессор скопировал код Physics.h в GameObject.h, который видит, что есть #include "GameObject.h", что означает копирование GameObject.h в себя. Когда вы копируете, вы снова получаете #include "Pysics.h", и вы застряли в цикле навсегда. Компиляторы предотвращают это, но это означает, что ваш #include выполнен наполовину.

Прежде чем говорить, как это исправить, вы должны знать другое.

Если у вас есть:

#include "b.h"

class A
{
    B b;
};

Затем компилятор должен знать все о b, что наиболее важно, какие переменные он имеет и т.д., чтобы он знал, сколько байтов должно помещать вместо b в A.

Однако, если у вас есть:

class A
{
    B *b;
};

Тогда компилятору не нужно ничего знать о b (поскольку указатели, независимо от типа, имеют одинаковый размер). Единственное, что ему нужно знать о b, это то, что он существует!

Итак, вы делаете что-то, называемое "forward declaration":

class B;  // This line just says B exists

class A
{
    B *b;
};

Это очень похоже на многие другие вещи, которые вы делаете в заголовочных файлах, например:

int function(int x);  // This is forward declaration

class A
{
public:
    void do_something(); // This is forward declaration
}

Ответ 2

Здесь вы имеете круглые ссылки: Physics.h включает GameObject.h, который включает Physics.h. В вашем классе Physics используется тип GameObject* (указатель), поэтому вам не нужно включать GameObject.h в Physics.h, а просто использовать форвардное объявление - вместо

#include "GameObject.h" 

положить

class GameObject;   

Кроме того, поместите защитные устройства в каждый файл заголовка.

Ответ 3

Проблема заключается в том, что ваш GameObject.h не имеет охранников, поэтому, когда вы #include "GameObject.h" в Physics.h, он включается, когда GameObject.h включает Physics.h.

Ответ 4

Добавить включить охранники во всех ваших заголовочных файлах *.h или *.hh (если у вас нет особых причин не делать этого).

Чтобы понять, что происходит, попробуйте получить предварительно обработанную форму исходного кода. С GCC это что-то вроде g++ -Wall -C -E yourcode.cc > yourcode.i (я не знаю, как это делают компиляторы Microsoft). Вы также можете спросить, какие файлы включены, с GCC как g++ -Wall -H -c yourcode.cc

Ответ 5

Во-первых, вам также нужно включить охранников в игровой объект, но это не настоящая проблема здесь.

Если что-то еще включает физику. Сначала, в физике .h есть gameobject.h, вы получите что-то вроде этого:

class GameObject {
...
};

#include physics.h

class Physics {
...
};

и #include physics.h отбрасывается из-за включенных охранников, и вы получаете объявление GameObject до объявления физики.

Но это проблема, если вы хотите, чтобы GameObject имел указатель на физику, потому что для физики htat нужно было бы объявить сначала.

Чтобы решить этот цикл, вы можете переадресовывать класс вместо этого, но только если вы просто используете его как указатель или ссылку в следующем объявлении, например:

#ifndef PHYSICS_H
#define PHYSICS_H

//  no need for this now #include "GameObject.h"

#include <list>

class GameObject;

class Physics
{
private:
    list<GameObject*> objects;
    list<GameObject*>::iterator i;
public:
    void ApplyPhysics(GameObject*);
    Vector2X CheckCollisions(Vector2X, GameObject*);
};

#endif // PHYSICS_H

Ответ 6

Используйте include guard в ВСЕ ваши файлы заголовков. Поскольку вы используете Visual Studio, вы можете использовать #pragma once в качестве первого определения препроцессора во всех своих заголовках.

Однако я предлагаю использовать классический подход:

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

Второе прочитано о форвардной декларации и примените его.

Ответ 7

Цель защиты заголовка - избежать многократного включения одного и того же файла. Но защита заголовка, которая в настоящее время используется в С++, может быть улучшена. Текущий охранник:

#ifndef AAA_H
#define AAA_H

class AAA
{ /* ... */ };

#endif

Мое новое охранное предложение:

#ifndef AAA_H
#define AAA_H

class AAA
{ /* ... */ };

#else
class AAA;  // Forward declaration
#endif

Это решает раздражающую проблему, которая возникает, когда классу AAA требуется объявление класса BBB, в то время как классу BBB требуется объявление класса AAA, как правило, потому что существуют перекрестные указатели от одного класса к другому:

// File AAA.h
#ifndef AAA_H
#define AAA_H

#include "BBB.h"

class AAA
{ 
  BBB *bbb;
/* ... */ 
};

#else
class AAA;  // Forward declaration
#endif

//+++++++++++++++++++++++++++++++++++++++

// File BBB.h
#ifndef BBB_H
#define BBB_H

#include "AAA.h"

class BBB
{ 
  AAA *aaa;
/* ... */ 
};

#else
class BBB;  // Forward declaration
#endif

Я хотел бы, чтобы это было включено в IDE, которые автоматически генерируют код из шаблонов.