49

解決済み

私が本当に助けてくれたのは、再定義されたエラーを発生させることなく、.cppファイルにヘッダーを#includeできることでした。


私はC++を初めて使用しますが、C#とJavaでプログラミングの経験があるため、C++に固有の基本的なものが不足している可能性があります。

問題は、何が問題なのかよくわからないことです。問題を説明するためにコードを貼り付けます。

GameEvents、Physics、GameObjectの3つのクラスがあります。それぞれにヘッダーがあります。GameEventsには、1つの物理学とGameObjectsのリストがあります。PhysicsにはGameObjectsのリストがあります。

私が達成しようとしているのは、GameObjectがPhysicsオブジェクトにアクセスまたは所有できるようにすることです。

GameObjectに単に「Physics.h」を#includeすると、「エラーC2111:'ClassXXX':'class'typeredifinition」が表示されます。そして、これが#include-guardsが役立つと思ったので、Physics.hにインクルードガードを追加しました。これは、2回インクルードしたいヘッダーだからです。

これはそれがどのように見えるかです

#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

しかし、GameObject.hに「Physics.h」を#includeすると、次のようになります。

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

なぜ表示されているのかわからない問題が複数発生します。「Physics.h」を#includeしない場合、コードは正常に実行されます。

助けてくれてとても感謝しています。

4

8 に答える 8

144

プリプロセッサは、プログラムを受け取り、いくつかの変更を加え(たとえば、インクルードファイル(#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

それでは、mainのsがどのように展開されるかを見てみましょう#include(これは、前のケースとまったく同じです:コピー-貼り付け)

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

OKA_Hが定義されました。継続する:

class A { int a; };

これはプリプロセッサ用のものではないので、そのままにしておきます。継続する:

#endif

前回ifはここで終了しました。継続する:

// From #include "b.h"

コメント。無視!継続する:

#ifndef B_H

B_H定義されていますか?いいえ!次に続行します。

#define B_H

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

ご覧のとおり、直接または間接にかかわらず、同じファイルでdを2回取得できるものは#includeすべて保護する必要があります。.hファイルは常に2回含まれる可能性が高いため、すべての.hファイルを保護することをお勧めします。

PSあなたは円形のsも持っていることに注意してください#include。プリプロセッサがPhysics.hのコードをGameObject.hにコピーして貼り付けることを想像してみてください。これは、それ自体に#include "GameObject.h"コピーGameObject.hすることを意味するが存在することを確認します。コピーすると、再び取得#include "Pysics.h"され、永遠にループに陥ります。コンパイラはそれを防ぎますが、それはあなた#includeのが半分終わっていることを意味します。

これを修正する方法を言う前に、あなたは別のことを知っているべきです。

あなたが持っている場合:

#include "b.h"

class A
{
    B b;
};

次に、コンパイラは、最も重要なこととして、どの変数を持っているかなどについてすべてを知る必要があります。これにより、コンパイラは、の代わりにb何バイトを配置する必要があるかを知ることができます。bA

ただし、次の場合:

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
}
于 2011-11-05T12:39:02.220 に答える
5

ここに循環参照があります:Physics.hincludeswhichincludes 。クラスは(ポインタ)型を使用するため、含める必要はなく、前方宣言を使用するだけです-代わりにGameObject.hPhysics.hPhysicsGameObject*GameObject.hPhysics.h

#include "GameObject.h" 

置く

class GameObject;   

さらに、各ヘッダーファイルにガードを配置します。

于 2011-11-05T12:26:46.417 に答える
4

問題は、あなたにGameObject.hは警備員がいないことです。そのため、あなた#include "GameObject.h"がそこにいるときはPhysics.h、が含まれているときにGameObject.h含まれますPhysics.h

于 2011-11-05T12:19:23.130 に答える
4

*.hすべてのファイルまたはヘッダーファイルにインクルードガードを追加し*.hhます(そうしない特別な理由がない限り)。

何が起こっているのかを理解するには、前処理された形式のソースコードを入手してみてください。GCCの場合、それは次のようなものですg++ -Wall -C -E yourcode.cc > yourcode.i(Microsoftコンパイラがそれをどのように行うかはわかりません)。また、GCCを使用して、どのファイルが含まれているかを尋ねることもできます。g++ -Wall -H -c yourcode.cc

于 2011-11-05T12:19:49.053 に答える
4

まず、ゲームオブジェクトにもガードを含める必要がありますが、それはここでの本当の問題ではありません

他の何かが最初にphysics.hを含み、physics.hがgameobject.hを含む場合、次のようなものが得られます。

class GameObject {
...
};

#include physics.h

class Physics {
...
};

また、インクルードガードが原因で#include physics.hが破棄され、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
于 2011-11-05T12:27:34.563 に答える
3

すべてのヘッダーファイルでインクルードガードを使用します。Visual Studioを使用しているため#pragma once、すべてのヘッダーで最初のプリプロセッサー定義としてを使用できます。

ただし、従来のアプローチを使用することをお勧めします。

#ifndef CLASS_NAME_H_
#define CLASS_NAME_H_

// Header code here

#endif //CLASS_NAME_H_

次に、前方宣言について読み、それを適用します。

于 2011-11-05T12:31:42.447 に答える
2

ヘッダーガードの目的は、同じファイルが何度も含まれないようにすることです。ただし、現在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にこれが含まれることを望んでいます。

于 2019-06-07T15:19:49.647 に答える
1

"#pragma once" :::ヘッダーガードと同じ目的を果たし、より短く、エラーが発生しにくいという追加の利点があります。

多くのコンパイラは、#pragmaディレクティブを使用したより単純な代替形式のヘッダーガードをサポートしています。 "#pragmaonce"//ここにコード

ただし、#pragmaはかつてC ++言語の公式部分ではなく、すべてのコンパイラがサポートしているわけではありません(ただし、最近のほとんどのコンパイラはサポートしています)。

互換性の目的で、従来のヘッダーガードを使用することをお勧めします。それらはそれほど多くの作業ではなく、すべての準拠コンパイラでサポートされることが保証されています。

于 2020-04-23T05:59:50.247 に答える