循環参照を回避できない

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
AXL

循環参照を回避できない

#1

投稿記事 by AXL » 13年前

3つのクラスがあり、
・スーパークラス
メンバ変数にQWEクラスのポインタを保持
・サブクラス
QWEクラスのメンバ関数を実行する
・QWEクラス
サブクラスのポインタからメンバ関数を実行する

QWEクラスでサブクラスが定義されていませんと出たので
循環参照なのだろうとスーパークラスのQWEの宣言を
class QWE qwe;
と治し、#include "QWE.h"を消したのですが、
サブクラスで"QWEが定義されていません"のような節のエラーがでます。
どうしたら良いですか?

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: 循環参照を回避できない

#2

投稿記事 by beatle » 13年前

根本的にクラス設計がまずそうな気はしますが、取り敢えず3つのクラスの定義を見せていただけますか?

AXL

Re: 循環参照を回避できない

#3

投稿記事 by AXL » 13年前

携帯なので多々誤字があるかもしれませんが・・÷

コード:

#include "ContactList.h"

class EntityBase{
 CContactList* ContactList;
};

コード:

#include "EntityBase.h"
class CPart : public EntityBase{
 Part(CContactList* tmpContactList){ContactList=tmpContactList;}
 void func(){ContactList->Entry(this)}
};

コード:

#include "Part"
#include <vector>
using namespace std;
class CContactList{
 vector<CPart*> List;
 void Entry(CPart* tmp){List.push_back(tmp);}
};
パソコンのモデムが死んでてコピペが一番だとは思うのですが直書きで失礼します。
流れはCPartクラスを継承した図形クラスがあるので其方をインスタンス化して
CContactListという当たり判定リストに登録します。
CContactList::Run()という関数があるのでそれを行うと
登録されたCPartが本来の図形にリキャストされて相互に判定を行い
結果をEntityBase::HitNotify()に、接触したCPartのポインタを返します


以上です。
わかりにくい・解決のために抜けている物があった場合すいません。

AXL

Re: 循環参照を回避できない

#4

投稿記事 by AXL » 13年前

コード:

#include "EntityBase.h"
#include "Part.h"
#include <vector>
using namespace std;
class Entity:public EntityBase{
 vector<CPart> Parts;
}
設計を指摘されていたので、もうひとつ
おそらくCContactListとこれ以外ではCPartとEntityBaseは使いません
ゲーム中の全てのオブジェクトはこれを継承して
建物やキャラを構成します。

ひょっとしたらこいつにPartをContactListに登録させれば循環参照は
起きないかもしれませんが
できることであればPartの振る舞いはPartにさせたいです

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: 循環参照を回避できない

#5

投稿記事 by beatle » 13年前

なるほど。拝見いたしました。
EntityBase#ContactList は要するに 親 みたいな感じですね。自分を含むリストへの参照。
これ自体は良くある設計なので良いと思います。

ではいくつか。
ポインタ型しか使わない場合、class宣言だけでOKです

コード:

class A;
class Hoge {
    A* a;
};
しかし、ポインタ経由でメソッド呼び出しなどをする時点では実装が見えてないといけません。

コード:

// A.hpp
class A {
    void something();
};

// Hoge.hpp
class A;
class Hoge {
    A* a;
    void f(); // ここでは実装できない
};

// Hoge.cpp
#include "Hoge.hpp"
#include "A.hpp"
void Hoge::f() {
    a->something(); // ここでは A.hpp をインクルードしたお陰で実装が見えているので、somethingを呼び出せる
}
とまあこんな感じですね。

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: 循環参照を回避できない

#6

投稿記事 by beatle » 13年前

これを元に AXL さんのプログラムの改良点を示すと
  • まず、クラスの実装を hpp と cpp に分けます。
  • hpp にはメンバ変数の宣言と、メンバ関数のプロトタイプ宣言だけを書きます。
    その際、できるだけ #include は少なくして、 class 何とか; の宣言だけで済ませるようにします。
    (つまり、ポインタ型しか使わないようなクラスは全部 class 何とか; の形で済ませます。今回の場合は CContactList と CPart です。ただし標準ライブラリのクラスは普通に#includeしましょう)
  • cpp にはメンバ関数の定義を書きます。
    その際、 hpp で class 何とか; したクラス定義を全部 #include します。
まとめると、 hpp では関数の中身を実装しないことがミソです。

AXL

Re: 循環参照を回避できない

#7

投稿記事 by AXL » 13年前

>beatleさん
返答ありがとうございます。
>しかし、ポインタ経由でメソッド呼び出しなどをする時点では実装が見えてないといけません。
なるほど、EntityBase.hでは"何かのクラス"という情報だけで、EntityBase.cppでContactList.hをincludeすれば回避できそうですね

USBメモリ持ってネットカフェに来たのでソースコードを貼らせて頂きます。^^;
せっかく、と言っては回答してくれる方に失礼かもしれませんが、これはまずいというようなところがありましたらご指摘お願いします。
※記述中に登場するCObjectはPosX,PosY,AngleとGetSetのみの構成です。
EntityBase.h

コード:

#ifndef DEF_EntityBase
#define DEF_EntityBase

#include "Object.h"
#include "ContactList.h"
#include <string>
using namespace std;

class CEntityBase : public CObject{
protected:
	string Name;

	int HitPoint;//PartにもHPを持たせて部位破壊を表現したい

	//子クラスのコンストラクタで決定してください。
	CContactList* ContactList;

public:
	CEntityBase();

	void SetName(string);

	void SetHitPoint(int);

	string GetName();

	int GetHitPoint();

	virtual void BattleNotifi()=0;
};

#endif

/*
EntityBase→Part
↓     ↓
Entity <- 各種形状
↓
各種オブジェクト

→継承
->保持
*/
Part.h

コード:

#ifndef DEF_Part
#define DEF_Part

#include "EntityBase.h"

//形状クラスはこれを継承する。
//Minecraftのようにダメージの種類によってメンバ関数を作成することはしない。
class CPart : public CEntityBase{
protected:

	//このPartを持つ親です。中身はEntityを継承したクラスになります。
	//子クラスのコンストラクタで決定してください。
	CEntityBase* ParentEntity;

	//このPartのEntityから見た識別子
	int Identifier;

	//このPartの形状
	//子クラスのコンストラクタで決定してください。
	int Shape;

	//部位が担当するイメージ
	int ImageHandle;

	//加害
	bool PerpetratorFlag;
	//被害
	bool VictimFlag;
	//接触
	bool ContactFlag;

	//描画
	bool DrawFlag;
	
public:

	CPart();

	//加害
	void SetPerpetratorFlag(bool);
	//被害
	void SetVictimFlag(bool);
	//接触
	void SetContactFlag(bool);

	void SetImageHandle(int);

	//描画
	void SetDrawFlag(bool);
	
	//識別子の設定
	void SetIdentifier(int);

	//識別子の取得
	int GetIdentifier();

	//形状の取得
	int GetShape();

	void Draw();

	void ContactListEntry();

	//PartではContactMapから呼び出されます。
	void BattleNotifi();
};

#endif
ContactList.h

コード:

#ifndef DEF_ContactList
#define DEF_ContactList

#include "Part.h"
#include "const.h"
#include <vector>
using namespace std;

//攻撃Partから被害Partに総当り判定を行い、結果を返します。
//また、渡された接触Partが1フレーム前の接触リストの接触エンティティと接触しているかどうかを判定します。
class CContactList{
private:

	//加害エンティティ
	vector<CPart*> PerpetratorList;
	//被害エンティティ
	vector<CPart*> VictimList;
	//接触エンティティ
	vector<CPart*> NewContactList;
	vector<CPart*> ContactList;//1フレーム前のPartの状態

	//当たっていたらtrueを返します。bool値は各オブジェクトに攻防通知をします。衝突の場合しません。距離、角度
	bool (CContactList::*Judge[SHAPENUM][SHAPENUM])(CPart*,CPart*,double*); //関数ポインタ配列

	bool JudgeLineCircle(CPart*,CPart*,double*);
	bool JudgeCircleLine(CPart*,CPart*,double*);
	bool JudgeCircleCircle(CPart*,CPart*,double*);
	bool JudgeLineLine(CPart*,CPart*,double*);

public:
	
	CContactList();

	void CombatRun();

	//1フレーム前の接触判定と現在のPartが接触していないかを確かめる
	//接触していた場合、返り値として接触対象のPartを返し、受け取ったEntityが反応を決めます。
	bool ContactCheck(CPart*,double*);

	void ContactListEntry(CPart*,bool,bool,bool);

};

#endif

AXL

Re: 循環参照を回避できない

#8

投稿記事 by AXL » 13年前

あ、すいません
質問は解決とさせて頂きます。

閉鎖

“C言語何でも質問掲示板” へ戻る