C ++ # включают охрану - PullRequest
       48

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

37 голосов
/ 05 ноября 2011

решено

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


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

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

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

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

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

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

#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

Но если я #include "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", мой код работает нормально.

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

Ответы [ 7 ]

115 голосов
/ 05 ноября 2011

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

Хорошо, теперь определено A_H.Продолжить:

class A { int a; };

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

#endif

Предыдущий if закончен здесь.Продолжить:

// From #include "b.h"

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

#ifndef B_H

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

#define B_H

Хорошо, теперь 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.

PS Обратите внимание, что у вас также есть циклические #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, это то, что он существует!

Итак, вы делаете что-то, что называется «предварительным объявлением»:

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
}
5 голосов
/ 05 ноября 2011

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

#include "GameObject.h" 

положить

class GameObject;   

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

4 голосов
/ 05 ноября 2011

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

Если что-то еще включает в себе Physicsh, то сначала Physics.h включает gameobject.h, и вы получите что-то подобное*

class GameObject {
...
};

#include physics.h

class Physics {
...
};

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

Но это проблема, если вы хотитеУ 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
4 голосов
/ 05 ноября 2011

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

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

4 голосов
/ 05 ноября 2011

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

3 голосов
/ 05 ноября 2011

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

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

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

Второе чтение о отправить декларацию и применить ее.

0 голосов
/ 07 июня 2019

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

#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, которые автоматически генерируют код из шаблонов.

...