C++ #включить охрану


Решено

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


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

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

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

То, что я пытаюсь добиться, это то, что я хочу, чтобы объект, чтобы быть в состоянии получить доступ или собственный объект физики.

Если я просто #включаю " физику.h " в GameObject я получаю "ошибка C2111: 'ClassXXX' : 'переопределенности тип класса", которые я понимаю. И вот тут я подумал, что #include-guards поможет, поэтому я добавил include guard в свою физику.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

Но если я #включаю " физику.h " в моем игровом объекте.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();
};

Я получаю множество вопросов, которые не понимают, почему они появляются. Если я не включу " физику.h " мой код работает просто отлично.

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

6 31

6 ответов:

Препроцессор-это программа, которая берет вашу программу, вносит некоторые изменения (например, включает файлы (#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
Теперь давайте посмотрим, как #includes в 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; };

Итак, как вы можете видеть, все, что может получить #included в одном файле дважды, нужно ли охранять его прямо или косвенно. Поскольку файлы .h всегда с большой вероятностью будут включены дважды, хорошо, если вы будете охранять все свои .H файлов.

Обратите внимание, что у вас также есть круговые #include s. представьте себе, что препроцессор копирует-вставляет код физики.ч в объект.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, это то, что он существует!

Итак, вы делаете что-то под названием "Прямая декларация":

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
}

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

#include "GameObject.h" 

Put

class GameObject;   

Кроме того, поставьте охрану в каждом заголовочном файле.

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

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

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

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

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

class GameObject {
...
};

#include physics.h

class 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

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

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

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

Во-вторых, прочитайте опрямом объявлении и примените его.