エラーの理由がわかりません。

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

エラーの理由がわかりません。

#1

投稿記事 by ??? » 16年前

こんにちは。ファイルを読み込むだけですが、読み込む量が非常に多いです。
そこで以下のようなプログラムを組んだのですが、わけのわからないエラーに陥っています。
解決策はわかったのですが理由がわかりません。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_SIZE 128
#define MAX_LINE 65536

int main(void){
	// test.txtを読み込む
	FILE *fp;
	fp = fopen("test.txt", "r");
	if(fp == NULL){
		printf("ファイルの読み込みに失敗しました。\n");
		exit(1);
	}

	// ファイルを最後まで読み込む]
	char buff[MAX_LINE][MAX_SIZE];
	int len;
	int i = 1;
	while(1){
		if(fgets(buff, MAX_SIZE, fp)==NULL)
			break;
		len = strlen(buff);
		if(len == 0 || buff[len - 1] != '\n'){
			if(feof(fp) == 0){
				printf("データが不正です[%s]。\n", buff);
				return 0;
			}
		}
		buff[len-1] = '\0';
		i++;
	}
	fclose(fp);
	return 0;
}


だとエラーが出て処理が強制終了するのに
// ファイルを最後まで読み込む]
char buff[MAX_LINE][MAX_SIZE];

// ファイルを最後まで読み込む]
static char buff[MAX_LINE][MAX_SIZE];
にするとエラーが出ません。
static変数は勉強中あまり使っておらず(理屈としてはわかったような気にはなっていますが)、苦手なのですが、この変化によって正常に動いたり異常が発生したりする理由は何なのでしょうか?

すみませんが、よろしくお願いします。

たかぎ

Re:エラーの理由がわかりません。

#2

投稿記事 by たかぎ » 16年前

スタックオーバーフローが原因かと思います。

???

Re:エラーの理由がわかりません。

#3

投稿記事 by ??? » 16年前

回答有難うございます。
すいません。何故スタックオーバーフローが発生したか、その理由がわからず質問させていただきました。

toyo

Re:エラーの理由がわかりません。

#4

投稿記事 by toyo » 16年前

変数の宣言方法で変数用のメモリが確保される方法が変わります。
関数内で宣言された変数は普通は自動変数としてスタックという領域に確保されます。
スタックは大きさが限られているので大きな配列を宣言するとスタックの大きさを超えてしまい足りなくなることがあります。
これをスタックオーバーフローといいます。
スタックオーバーフローは大きな配列宣言のほかに再帰関数でも起こる可能性があります。
一方変数にstaticをつけると静的変数となり変数が別の領域に確保されます。スタックには影響しないのでスタックオーバーフローを回避できます。変数を関数の外に出してグローバル変数にしても同様にスタックに影響しないので大丈夫です。
VisualC++のスタックは標準で1Mバイトだったと思います。8Mバイトの配列では明らかに足りませんね。
スタックのサイズはリンカのオプションで変更できますが普通はスタックを増やすよりプログラム設計を見直したほうがいいでしょう。

???

Re:エラーの理由がわかりません。

#5

投稿記事 by ??? » 16年前

>toyo様
有難うございます。
設計を見直すといっても、読み込みもとのファイルがウン十万行を余裕で超えているので、どうしても配列が必要なのです・・・

おっしゃるとおりちょっと考えて見ますね

バグ

Re:エラーの理由がわかりません。

#6

投稿記事 by バグ » 16年前

その数十万行のデータを一時的に分割保存する事は出来ないのでしょうか?

更に言えば、分割する際に一定のルールを設けてみてはいかがでしょうか?

例えば、なんらかの数値データが保存されているとして、0~99の場合はファイル1へ、100~199のファイル2へ…みたいに、元データファイルのデータを1つ読み込む度に別のファイルへ落としていき、必要に応じてそれぞれのファイルから再度データを抜き出す…みたいな感じでどうでしょうか?

無論、全てのデータを保持しておかなければいけない場合はこの方法は使用できませんが…

山崎

子クラスの静的メンバの値を正しく取得できない

#7

投稿記事 by 山崎 » 16年前

お世話になっております、山崎です。
今回は、静的メンバのアクセスに関してお伺いに参りました。

現在、引き続き敵キャラに関する処理を作っております。
親クラスにグラフィックハンドルとなる変数を持たせ、
そのメンバを子クラスで静的メンバにしてオーバーライドしました。
グラフィックハンドルにLoadGraphする処理は、親クラスのメソッドにしています。

以下のコードのように、
子クラスの静的メンバに、親クラスのメンバ関数内でグラフィック領域を確保しようとしています。
親クラスのメンバ関数内では、クラス定義の外でNULLとしているはずの子クラスの静的メンバの値が
おかしなものになっています(親クラスのメンバにアクセスしてしまっているのだと思います)。
#include "DxLib.h"

class Parent
{
public:
	int* GHandle;

	void LoadGH(char* GHName)
	{
		if(GHandle==NULL)
		{
			GHandle=new int();
			*GHandle=LoadGraph(GHName);
		}
	}

	void PrintGH(int X,int Y)
	{
		DrawGraph(X,Y,*GHandle,TRUE);
	}
};

class Child1:public Parent
{
public:
	static int* GHandle;

	Child1()
	{
		LoadGH("Img1.bmp");
	}
};
int* Child1::GHandle=NULL;

class Child2:public Parent
{
public:
	static int* GHandle;

	Child2()
	{
		LoadGH("Img2.bmp");
	}
};
int* Child2::GHandle=NULL;

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	ChangeWindowMode(TRUE);

	if( DxLib_Init() < 0 ) 
		return -1;

	Child1 C11,C12;
	Child2 C21,C22;

	while(ProcessMessage()==0)
	{
		ClsDrawScreen();

		C11.PrintGH(10,10);
		C12.PrintGH(10,250);
		C21.PrintGH(330,10);
		C22.PrintGH(330,250);

		ScreenFlip();
	}

	DxLib_End();
	return 0;
}
親クラスのメンバ関数内で、子クラスでオーバーライドした静的メンバに
正しくアクセスするにはどのようにしたらよいのでしょうか。

ねこ

Re:子クラスの静的メンバの値を正しく取得できない

#8

投稿記事 by ねこ » 16年前

どういうスコープの時にどうおかしいのかが分からないです。

LoadGH関数時にグラフィックハンドルが入っているのは「Parent::GHandle」ですよ。
とりあえず子クラスのコンストラクタに下記を追加すればいいのかな?
if( GHandle == NULL ) {
	LoadGH( "Img1.bmp" );
	GHandle = Parent::GHandle;
}
気になるのはParentにコンストラクタが無く「GHandle」がNULL初期化されていないから
いつまでもメモリ確保されないような・・・

dic

Re:子クラスの静的メンバの値を正しく取得できない

#9

投稿記事 by dic » 16年前

Parentクラスを継承しているなら
子クラスで静的メンバにしてオーバーライドする理由は見当たりませんが

dic

Re:子クラスの静的メンバの値を正しく取得できない

#10

投稿記事 by dic » 16年前

修正してみました
#include "DxLib.h"

class Parent {
public:
	int* GHandle;

	void LoadGH(char* GHName) {
		if(GHandle==NULL) {
			GHandle=new int();
			*GHandle=LoadGraph(GHName);
		}
	}

	void PrintGH(int X,int Y) {
		DrawGraph(X,Y,*GHandle,TRUE);
	}

	Parent();
	~Parent();
};

Parent::Parent() {
	GHandle = 0;
}
Parent::~Parent() {
	if( GHandle ) {
		delete GHandle;
		GHandle = NULL;
	}
}
class Child1	:	public Parent {
public:
	Child1() {
		LoadGH("Img1.bmp");
	}
};

class Child2	:	public Parent {
public:
	Child2() {
		LoadGH("Img2.bmp");
	}
};

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	ChangeWindowMode(TRUE);

	if( DxLib_Init() < 0 ) 
		return -1;

	Child1 C11,C12;
	Child2 C21,C22;

	while(ProcessMessage()==0)
	{
		ClsDrawScreen();

		C11.PrintGH(10,10);
		C12.PrintGH(10,250);
		C21.PrintGH(330,10);
		C22.PrintGH(330,250);

		ScreenFlip();
	}

	DxLib_End();
	return 0;
}

ねこ

Re:子クラスの静的メンバの値を正しく取得できない

#11

投稿記事 by ねこ » 16年前

>dicさん
これだとインスタンス生成する度にファイルからグラフィックハンドル作っちゃいませんか?
多分それを回避するために子にstatic変数を用意してるんじゃないのかな~と。

山崎

Re:子クラスの静的メンバの値を正しく取得できない

#12

投稿記事 by 山崎 » 16年前

ねこさん、dicさん、ご返信誠にありがとうございます。
お返事遅くなって申し訳ございません。

>ねこさん
言葉足らずで申し訳ございません。
C11を宣言したときに、親クラスであるParentクラスで定義されているLoadGH()が
子クラスChildのコンストラクタ内で呼ばれるのですが、
クラス定義の外側で
int* Child1::GHandle=NULL;
とGHandleはNULLで初期化されているはずなのに、
LoadGH()内ではNULLではない値(0xcccccccc)になっています。

親クラスのGHandleに関してですが、
親クラスParentは子クラスに継承させるためだけに宣言しておりまして、
親クラスはインスタンス化せず、親クラス自体はGHandleを使用することがないので初期化しておりません。
その代わり、GHandleを利用する子クラスではクラス定義の外側でGHandle=NULLと初期化しております。

子クラスの静的変数GHandleがNULLで初期化され、
メンバ関数LoadGH()の
if(GHandle==NULL)
にひっかかりそのif文内でグラフィックをロードする…という処理をする、
というつもりのコードになっております。
一度GHandleにグラフィックをロードすればGHandleはNULLでなくなるので、
LoadGH()に入ってももうそれ以上同じグラフィックがロードされることはないはず、となると思ったのですが…。

なお、おっしゃるとおり
子クラスで親クラスのメンバを静的メンバとしてオーバーライドするのは
子クラスのインスタンスが生成されるたびに
同じ絵なのに新しいグラフィック領域が確保されてしまうのを防ぐためです。

親クラスのコンストラクタ内で
GHandle=NULL;
とした場合、子クラスのインスタンスが生成されるたびに親コンストラクタ内で
子クラスの静的メンバGHandleがNULLになるので、その後のLoadGH()のif(GHandle==NULL)内に入り、
既にグラフィック領域を確保したGHandleにまたグラフィック領域を確保してしまうのではないでしょうか?

>dicさん
ねこさんのおっしゃるとおり、
インスタンスが生成されるたびに同じ絵のグラフィック領域を確保されるのを防ぐため、
子クラスではGHandleを静的メンバとしてオーバーライドしております。

ねこ

Re:子クラスの静的メンバの値を正しく取得できない

#13

投稿記事 by ねこ » 16年前

>int* Child1::GHandle=NULL;
>とGHandleはNULLで初期化されているはずなのに、
>LoadGH()内ではNULLではない値(0xcccccccc)になっています。
まずここが間違いですね。LoadGH関数で見えている「GHandle」はParent::GHandleです。
同名変数があっても関数が親に属したままです。
そのためGHandleに未初期化値の「0xccc~」が入ってます。

今試せないのですが、LoadGHにvirtual定義を入れたら上手く動かないでしょうか?

山崎

Re:子クラスの静的メンバの値を正しく取得できない

#14

投稿記事 by 山崎 » 16年前

>ねこさん
ご返信ありがとうございます。

今LoadGH()を
virtual void LoadGH()
としてデバッグしてみましたが、LoadGH()の中ではやはりParent::GHandleに
アクセスしてしまっているようです。
GHandleは0xccccccになっていて、if(GHandle==NULL)の中に入れませんでした。

Justy

Re:子クラスの静的メンバの値を正しく取得できない

#15

投稿記事 by Justy » 16年前


>LoadGH()内ではNULLではない値(0xcccccccc)になっています。

 LoadGH内ではねこさんが最初に書いている通り、 Parent側の GHandleに
対してアクセスしています。

 staticな変数ではないので、未初期化状態なのでしょう。



>親クラスParentは子クラスに継承させるためだけに宣言しておりまして
>~
>親クラスはインスタンス化せず、親クラス自体はGHandleを使用することがないので初期化しておりません

 正直意味がわからない半分、なんとなく判る半分なのですが、
根本的にそれらは別物の変数です。

 親クラスはインスタンス化しない、とありますが、
言語的にしっかりとインスタンス化されています。



>インスタンスが生成されるたびに同じ絵のグラフィック領域を確保されるのを防ぐため、
>子クラスではGHandleを静的メンバとしてオーバーライドしております。

 オーバーライドできるのはメンバ関数であって、変数ではありませんよ。

 さて、改善案ですが、根本的に子クラスの staticメンバにテクスチャハンドルを入れるまでは
まだいいのですが、それを1つしかないと決めうちして基底クラスでテクスチャをロードする設計は
テクスチャが複数枚になった瞬間対応出来なくなります。


 設計的に同じテクスチャの再読込を回避するなら、別の手法を使うべきでしょう。

 例えば、複数のテクスチャを管理するクラスを作り、エネミークラスは
ハンドルを格納する変数をメンバに持たせた上で、そこに対し
必要なテクスチャをリクエストします。

 テクスチャ管理クラスはそのテクスチャがまだ読み込まれていなければ
管理クラス内でロードを行い、そのハンドル値を記憶してからエネミークラスに
ハンドルを返します。
 既に読み込み済みなら、記憶していたハンドルを返します。

 これなら同じテクスチャを何度も読みことはなくなりますし、
解放処理も楽にできます。


 ちなみにこのような別の手法ではなく、現時点での方法の延長線上でなんとかしたいと
いうことでしたら最小限の修正&オーソドックスな手法としては仮想関数を
使うという手があります。
[color=#d0d0ff" face="monospace]
class Parent
{
public:
void LoadGH(char* GHName)
{
int & handle = GetGHandle();
if(handle==0)
handle=LoadGraph(GHName);
}

void PrintGH(int X,int Y)
{
DrawGraph(X,Y,GetGHandle(),TRUE);
}

virtual int& GetGHandle() = 0;
};

class Child1:public Parent
{
public:
static int GHandle;

Child1() { LoadGH("Img1.bmp"); }
virtual int& GetGHandle() { return GHandle; }
};
int Child1::GHandle=NULL;

class Child2:public Parent
{
public:
static int GHandle;

Child2() { LoadGH("Img2.bmp");}
virtual int& GetGHandle() { return GHandle; }
};
int Child2::GHandle=NULL;
[/color]

 ただ、まぁ自分で書いてて何ですが……微妙ですね。

dic

Re:子クラスの静的メンバの値を正しく取得できない

#16

投稿記事 by dic » 16年前

なるほど そういう設計なのですね
私は、たとえ同じグラフィックでも別々で確保するので設計が異なるのでしょう

static メンバ変数を利用してるのはそういう理由だったんですね
なので static メンバ変数が使えるので利用させてもらって作り直しました
#include "DxLib.h"

class Parent
{
public:
//	int* GHandle;

	void PrintGH(int X,int Y,int *handle)
	{
		DrawGraph(X,Y,*handle,TRUE);
	}
};

class Child1:public Parent
{
public:
	static int* GHandle;

	Child1()
	{
		if( GHandle == NULL ) {
			GHandle = new int;
			*GHandle = LoadGraph("Img1.bmp");
		}

	}
};
int* Child1::GHandle=NULL;

class Child2:public Parent
{
public:
	static int* GHandle;

	Child2()
	{
		if( GHandle == NULL ) {
			GHandle = new int;
			*GHandle = LoadGraph("Img2.bmp");
		}
	}
};
int* Child2::GHandle=NULL;

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
	ChangeWindowMode(TRUE);

	if( DxLib_Init() < 0 ) 
		return -1;

	Child1 C11,C12;
	Child2 C21,C22;

	while(ProcessMessage()==0)
	{
		ClsDrawScreen();

		C11.PrintGH(10,10,C11.GHandle);
		C12.PrintGH(10,250,C12.GHandle);
		C21.PrintGH(330,10,C21.GHandle);
		C22.PrintGH(330,250,C22.GHandle);

		ScreenFlip();
	}

	DxLib_End();
	return 0;
}

山崎

Re:子クラスの静的メンバの値を正しく取得できない

#17

投稿記事 by 山崎 » 16年前

Justyさん、dicさん、ご返信誠にありがとうございます。

>Justyさん
親クラスのメンバと同名のメンバを子クラスで宣言しても、別のものとみなされるのですね…。
メンバ変数のオーバーライドのことなど、今までずっと勘違いをしてきたようです。
全くお恥ずかしい限りです、穴があったら顔から火を出しながら入りたいです…。

意味不明な質問で本当にすいません、
私なりに極力整理して書いたつもりですが、やはり精進不足…申し訳ないです。
とにかく経験をつんでまいりたいと思います。

さて本題の方ですが、
提示したコードは今作っているプログラムの問題の箇所を抽象的に簡単にしたもので、
実際のコードはもうちょっと複雑になっております。
実際は、GHandleはint**型で、LoadGH()内でint型の2次元配列にグラフィックを格納しております。
正面向きの絵が何枚で右向きの絵が何枚のキャラ、正面向きの絵しかないキャラなど、
キャラに応じて絵の枚数は変えられるようにしています。
なのでGhandleは、どうしてもint型のポインタ(のポインタ)にしたいと思っています。

やはり、グラフィックはグラフィックの管理専用のクラスにまとめるのがスタンダードなのでしょうか?
一番初めにプログラムの大まかな設計を考える段階では
全てのグラフィックを管理するクラスを作るという案はあったのですが、
結局、こんな問題に突き当たるとは思わず今の設計で作ることを何となく選んでしまいました。

今の設計のまま進めるのはちょっと私の技術じゃ難しいようなので、
書き換える手間はかかるかもしれませんが全てのグラフィックを管理するクラスを用いる方法に変えたいと思います。

>dicさん
わざわざコードを提示してくださり誠にありがとうございます。
しかしながら、今までの方法では私の手に負えなくなってきましたので、
ちょっと手法を変えていくことにいたしました。
せっかく提示していただいたのに申し訳ございません。

ちなみに、実際のプログラムでは、親クラスEnemyで
こんな風にグラフィックをロードしていました。

void Enemy::MakeHandle(char* NEnemyName)
{
	//グラフィック領域を確保
	if(GHandle==NULL)
	{
		char FileName[100];
		GHandle=new int*[4];//右、左、正面、後ろの4方向のグラフィック
		for(int i=0;i<4;i++)
		{
			GHandle=new int[GHNumbe[/url];//1方向につき、GHNumber枚のグラフィックを確保
			for(int j=0;j<GHNumber;j++)
			{
				if(i!=2)//右向き以外のグラフィックをロード
				{
					if(i==0)//右向きのグラフィックのロード
					{
						sprintf(FileName,"ImageFiles\\Enemy\\%s\\%s%d_%d.bmp",NEnemyName,NEnemyName,i,j);
						GHandle[j]=LoadGraph(FileName);
					}
					else//i==1 || i==3 正面向き、後ろ向きのグラフィックのロード
					{
						if(GHDirection==2)
						{
							sprintf(FileName,"ImageFiles\\Enemy\\%s\\%s%d_%d.bmp",NEnemyName,NEnemyName,i,j);
							GHandle[j]=LoadGraph(FileName);
						}
						else
							GHandle[j]=GHandle[0][j];
					}
				}
				else//右向きのグラフィックをロード
				{
					if(GHDirection!=0)
					{
						int XSize,YSize;
						GetGraphSize(GHandle[0][j],&XSize,&YSize);
						GHandle[j]=DerivationGraph(XSize-1,0,-XSize,YSize,GHandle[0][j]);
					}
					else
						GHandle[j]=GHandle[0][j];
				}

			}
		}
	}
}

Justy

Re:子クラスの静的メンバの値を正しく取得できない

#18

投稿記事 by Justy » 16年前


>実際は、GHandleはint**型

 なるほど、そうでしたか。



>グラフィックはグラフィックの管理専用のクラスにまとめるのがスタンダードなのでしょうか?

 特にそういうわけではないです。
 全部を1つに、というのはあまりないかと。

 結局のところ、規模とか状況にも依るわけですが、リソースの使用状況とか寿命を
管理する為の何らかの仕組みはあった方がいいとは思いますが、それがリソースを
まとめるクラスであるかどうかは、状況次第になります。

 単純に個々の末端でのコンストラクタ・デストラクタでのリソースの読み込み・破棄で済むことも
あるでしょうし、今回のようにクラスに静的メンバを置いて管理した方がいいこともありますので。



>全てのグラフィックを管理するクラスを用いる方法に変えたいと思います

 勧めておいて何ですが、性急に決めないでもう少し検討してもいいかと思いますよ。
 今から変えるのは手間ですし、それで設計上問題ないか検討は十分に行なわないと
後でいろいろ問題がおきるかもしれませんし。


 で、ある程度状況が判ったところで、もう今の延長線上での提案を。
 Enemy::MakeHandleのような処理を基底に入れるなら、こうしてはどうでしょうか。

 Enemy::MakeHandleはローカル変数として宣言した int **に対してnewを行い、様々な処理を行ったあと、
そのポインタを戻り値として返し、派生側で staticメンバに代入を行う、という方法です。

 これなら派生先の情報を基底に持ち込まなくて済むので、だいぶマシになるかと思います。
 どうでしょう。


(あ、あとできるなら、グラフィックハンドルは intではなくテクスチャクラスを使って管理し
さらに vectorとかのコンテナでまとめて管理するようにしておいてほしいところです)

山崎

Re:子クラスの静的メンバの値を正しく取得できない

#19

投稿記事 by 山崎 » 16年前

Justyさん
リソースの扱い方は状況次第、なのですね。
私はまだプロのプログラマではなく取引されるようなプログラムを見た事が無いので、
自分のプログラムが常識はずれな組み方なんじゃないかとよく心配になります。
この子クラスの静的メンバに親クラスのメンバ関数内でアクセスしてグラフィックをロード、
という方法はプロに見せたら笑われるんじゃないか、とちょっと心配してました。

 >Enemy::MakeHandleはローカル変数として宣言した int **に対してnewを行い、様々な処理を行ったあと、
  そのポインタを戻り値として返し、派生側で staticメンバに代入を行う

とは、このような感じでしょうか?
class Enemy:public BattleObject
{
public:
	int** GHandle;
	short GHNumber;//1方向につき何枚の絵を持つか
	short GHDirection;//正面向きの絵だけのキャラか、左右の2つの向きの絵があるキャラか、4方向全ての絵があるキャラか

	void PrintGH(){...};

	int** MakeGHandle(char* NEnemyName)
	{
		//グラフィック領域を確保
		if(GHandle==NULL)
		{
			char FileName[100];
			GHandle=new int*[4];//右、左、正面、後ろの4方向のグラフィック
			for(int i=0;i<4;i++)
			{
				GHandle=new int[GHNumbe[/url];//1方向につき、GHNumber枚のグラフィックを確保
				for(int j=0;j<GHNumber;j++)
				{
					//GHDirection,GHNumber,NEnemyNameにしたがってGHandle[j]にグラフィックのロードを行う
					...
				}
			}
		}

		return GHandle;
	}

};

class ChildEnemy:public Enemy
{
public:
	static int** GHandle;
	static short GHDirection;
	static short GHNumber;

	ChildEnemy()
	{
		if(GHandle==NULL)
			GHandle=MakeGHandle("ChildEnemy");
	}
};
int** ChildEnemy::GHandle=NULL;
short ChildEnemy::GHDirection=2;
short ChildEnemy::GHNumber=3;


上のコードのEnemyクラスにはGHDirectionとDHNumberというメンバがありますが、
これはグラフィックをロードする際に使うメンバで、
GHDirectionはそのキャラが向き別のグラフィックを持つかどうかを表し、
GHNumberはそのキャラが1方向の向きにつき何枚のグラフィックを持つかを表しています。
これらのメンバはひとつのクラスにひとつずつあればいいので、
実際のプログラムでは上のコードのように子クラスで静的メンバにして、親のMakeGHandle()内で使用しています。

グラフィックをロードするだけならMakeGHandle()の引数で
向きや枚数の情報を指定してやればいいのですが、使い終わったグラフィックを解放するときに、
どの向きの絵を何枚確保していたかを記憶していないと正しくグラフィック領域を解放できません。

また、是非ともJustyさんのご助言どおり、
テクスチャクラスやvectorでの管理を実践したいと思っているのですが、
「テクスチャクラス」とは何でしょうか?
グラフィックハンドルを専門に扱うクラスを自分で定義したもの、でしょうか?

いろいろご助言頂き、本当にありがとうございます。

Justy

Re:子クラスの静的メンバの値を正しく取得できない

#20

投稿記事 by Justy » 16年前


>とは、このような感じでしょうか?

 いえ、それだと Enemyクラスのメンバ GHandleを返していますよね?
 Enemyクラスは GHandleは持ちません。
 あくまで、Enemy::MakeGHandle()のローカル変数上で int**からデータを構築し、
戻り値としてそのポインタのポインタを戻します。

 ちょっと強引なサンプルですが、
[color=#d0d0ff" face="monospace]
class Enemy
{
public:
int** MakeGHandle(char* GHName)
{
int **handle = new int*[1];
handle[0] = new int[1];
handle[0][0] = LoadGraph(GHName);
return handle;
}
};

class Child1:public Enemy
{
public:
static int **GHandle;

Child1()
{
if(!GHandle)
GHandle = MakeGHandle("Img1.bmp");
}
};
int **Child1::GHandle=NULL;

class Child2:public Enemy
{
public:
static int **GHandle;

Child2()
{
if(!GHandle)
GHandle = MakeGHandle("Img2.bmp");
}
};
int **Child2::GHandle=NULL;
[/color]




>グラフィックハンドルを専門に扱うクラスを自分で定義したもの、でしょうか?

 そうです。
 1つのテクスチャを1テクスチャクラスが管理し、生成から破棄までを
コントロールするようなクラスです。
 そうしておくと、生成はともかく、破棄は楽になります。

 たとえば、そのテクスチャクラスを DxLib::Textureとしたのなら、
[color=#d0d0ff" face="monospace]
 DxLib::Texture *tex = new DxLib::Texture[3];
 tex[0]->LoadFile("a.bmp");
 tex[1]->LoadFile("b.bmp");
 tex[2]->LoadFile("b.bmp");
 delete[/url] tex;
[/color]

 としたとき、最後の deleteで3つの DxLib::Textureクラスがデストラクタで
それぞれ DeleteGraphもやってくれたら楽ですよね?

山崎

Re:子クラスの静的メンバの値を正しく取得できない

#21

投稿記事 by 山崎 » 16年前

Justyさん
お返事遅れてしまい申し訳ございません。
アドバイスを元にプログラムを修正しておりました。
おかげさまで、期待通りの動きのプログラムに修正することができました。

ご助言頂いたように、新たにTextureクラスを作成しました。
Enemy::MakeGHandle()もTexture**を返すようにしてみました。
確かに、グラフィック領域の解放がかなり楽にできますね。
これからはこの手法を用いていこうと思います。
class Texture
{
public:
	int GHandle;
	bool Original;//デストラクタが呼ばれたとき、グラフィック領域を開放するかどうか

	Texture();//引数なしコンストラクタ。配列をnewしたときのため。
	Texture(char* GHName);//グラフィックのファイル名を指定して新たにグラフィック領域を確保
	Texture(Texture& Tex);//既に確保されたグラフィックのハンドルをコピー。領域は確保しない。

	Texture::~Texture()
	{
		if(Original==true)
			DeleteGraph(GHandle);
	}

	//引数なしコンストラクタでインスタンスを生成した場合、後からハンドルを設定する関数たち
	void MakeHandle(char* GHName);
	void MakeHandle(Texture& Tex);
	void MakeHandle(int NGH);

	int Texture::GetHandle()//グラフィックハンドルを返す
	{
		return GHandle;
	}
};

Texture** Enemy::MakeHandle(short GHDir, short GHNum, char *EnemyName,Texture** GH)
{
	//グラフィック領域を確保
	if(GH==NULL)
	{
		char FileName[100];
		Texture** Handle=new Texture*[4];//右、左、正面、後ろの4方向のグラフィック
		for(int i=0;i<4;i++)
		{
			Handle=new Texture[GHNum];//1方向につき、GHNumber枚のグラフィックを確保
			for(int j=0;j<GHNum;j++)
			{
				if(i!=2)//右向き以外のグラフィックをロード
				{
					if(i==0)//右向きのグラフィックのロード
					{
						sprintf(FileName,"ImageFiles\\Enemy\\%s\\%s%d_%d.bmp",EnemyName,EnemyName,i,j);
						Handle[j].MakeHandle(FileName);
					}
					else//i==1 || i==3 正面向き、後ろ向きのグラフィックのロード
					{
						if(GHDir==2)
						{
							sprintf(FileName,"ImageFiles\\Enemy\\%s\\%s%d_%d.bmp",EnemyName,EnemyName,i,j);
							Handle[j].MakeHandle(FileName);
						}
						else
							Handle[j].MakeHandle(Handle[0][j]);
					}
				}
				else//右向きのグラフィックをロード
				{
					if(GHDir!=0)
					{
						int XSize,YSize;
						GetGraphSize(Handle[0][j].GetHandle(),&XSize,&YSize);
						Handle[j].MakeHandle(DerivationGraph(XSize-1,0,-XSize,YSize,Handle[0][j].GetHandle()));
					}
					else
						Handle[j].MakeHandle(Handle[0][j]);
				}

			}
		}

		return Handle;
	}
	else
		return GH;
}


おかげさまで、これまでの設計を大きく変更することなく、
子クラスの静的メンバにグラフィック領域を確保することができました。
本当に、いろいろ丁寧にご助言くださりありがとうございます。

恐縮ながら最後にひとつお伺いしたいのですが、
vectorなどのコンテナでまとめるというのは、Textureクラスを、でしょうか?
Texture**型のポインタにnewで2次元配列を確保し、グラフィックをそこで管理するのではなく、
static vector<Texture> EnemyChild::Textures;
のようにTexture型を格納するvectorなどを用意し、そこで管理したほうがいい、
という理解でよろしいでしょうか。

Justy

Re:子クラスの静的メンバの値を正しく取得できない

#22

投稿記事 by Justy » 16年前


>新たにTextureクラスを作成しました

 多分掻い摘んで書いてある Textureクラスだと思うので省略されてしまっているだけなのかも
しれませんが、このクラスはコピー対策はしてありますか?
 していないのでしたら、しておいた方がいいかと思います。

 コピーされると、Originalメンバが trueなのが複数生まれ、
どれか1つが破棄された段階で終わってしまいます。



>Texture**型のポインタにnewで2次元配列を確保し、グラフィックをそこで管理するのではなく、
>static vector<Texture> EnemyChild::Textures;
>のようにTexture型を格納するvectorなどを用意し、そこで管理したほうがいい

 そういうことになります。
 
 今はポインタ2つで二次元配列のように使っていますが、
今回の用途を考えると1次元でも十分だと思います。

 使用するテクスチャの枚数は 4 * GHNumであり、計算すれば右の2枚目、とかは
別に handle[2][1]とかでなくても、handle[2 * GHNum + 1]のようにアクセスすれば
十分区別できます。

 int GetHandle(右, 1);

 のようなメンバ関数でも作っておけば間違えることもないでしょうし。


 ただ、1点だけ。
 最終的なテクスチャの解放は DxLib_End()よりも前に行わなければならないので、
使ったコンテナは必ず DxLib_End()よりも前にクリアしておいてください。

山崎

Re:子クラスの静的メンバの値を正しく取得できない

#23

投稿記事 by 山崎 » 16年前

Justyさん
Textureクラスのコピー対策は恥ずかしながらしっかりと怠っておりました…。
コピーコンストラクタはこのように定義しました。
Texture::Texture(const Texture& Tex)
{
	GHandle=Tex.GHandle;
	Original=false;
}
先ほど子Enemyクラスの静的メンバint** GHandlesも、
vector<Texture*> GHandlesに修正しました。
修正にだいぶ手間取りましたが、今は敵キャラたちも元気に動いております。

何から何までいろいろ教えていただき本当に感謝の言葉もございません。
Justyさんのような方々がいるおかげで、めげずにプログラムを続けていくことができます。

閉鎖

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