std::listをメンバにもつクラスを削除しようとするときに例外が投げられる問題

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

std::listをメンバにもつクラスを削除しようとするときに例外が投げられる問題

#1

投稿記事 by Hiragi(GKUTH) » 6年前

いつもお世話になっております。

現在、STGを作成しており、敵の弾をstd::listで管理しているのですが、敵のクラスを削除するとき(恐らく)listの処理の中で例外が投げられて
動作がストップしてしまう問題に困っております。この例外の原因解明、および問題の解決方法はあるでしょうか。

Windows 10 Build 1607
C++ DxLibrary
VIsual Studio 2017 community

以下に該当部分のソースを提示します。
設計としては、Managerクラスがplayer enemy などゲーム内の要素のクラスを持っており、
削除や登録を行っています。例外はManager.cpp 37行目で投げられます。例外が投げられた場所はlist内の
1546行目です。
また、Manager::All()は毎フレーム呼び出されます。

回答よろしくお願いします。

Manager.hpp

コード:

#include "Player.hpp"
#include "Enemy.hpp"
#include "Back.hpp"

#define TMP 16

class Manager
{
private:
	const int ENEMY_MAX = 16;

	Board *board;
	Player *player;
	Enemy *enemy[TMP];

		//敵データ読み込み用
	typedef struct
	{
		int ID;
		float x;
		float y;
		int in_time;
		int stop_time;
		int shot_time;
		int out_time;
		int move_kind;
		int shot_kind;
	}EnemyData;

	EnemyData enemydata[TMP];

public:
	Manager();
	~Manager();
	void All();
	void LoadEnemyData();
	void EnterEnemy(int argIndex);
};
Manager.cpp

コード:

//少なくともゲームのメインシーンにおいて各々を管理するためのクラスです。
//後々シーン管理なり始めたらなにかしらのクラスの継承先になる可能性があります。

#include "Manager.hpp"
#include "System.hpp"
#include <fstream>
#include <string>
#include <iostream>
#include <sstream>

Manager::Manager()
{
	player = new Player();
	board = new Board();
	for (int i = 0; i < ENEMY_MAX; i++)
	{
		enemy[i] = new Enemy();
	}
	for (int i = 0; i < ENEMY_MAX; i++)
	{
		enemydata[i].ID = -1;	//IDが-1なら未登録であるということにする
	}
	LoadEnemyData();
}

void Manager::All()
{
	board->All();
	player->All();

	for (int i = 0; i < ENEMY_MAX; i++)
	{
			//敵が存在しなくなったら消す
		if (enemy[i]->IsDestroyed())
		{
			enemy[i]->~Enemy();
			delete enemy[i]; //ここで例外が投げられる
		}
			//敵が登録されていて、かつ登場すべき時間であれば敵を登録する
		if (enemydata[i].ID != -1 && enemydata[i].in_time == System::Instance()->GetCount())
		{
			EnterEnemy(i);
		}

			//有効な敵だけ処理する
		if (enemy[i]->IsStarting())
		{
			enemy[i]->All();
		}	
	}
}

	//とりあえず関数だけ分けてみたり
void Manager::EnterEnemy(int argIndex)
{
	enemy[argIndex]->Init(enemydata[argIndex].x,			enemydata[argIndex].y,			enemydata[argIndex].in_time,
						  enemydata[argIndex].stop_time,	enemydata[argIndex].shot_time,	enemydata[argIndex].out_time,
						  enemydata[argIndex].move_kind,	enemydata[argIndex].shot_kind);

}

	//クソ設計過ぎて泣ける
void Manager::LoadEnemyData()
{
	std::ifstream EnemyCsv("./assets/csv/Enemy00.csv");	//ファイル開く
	std::string buf;	//バッファ
	int row = 0;	//行
	int column = 0;	//列
	
	std::getline(EnemyCsv, buf);	//一行読み飛ばして
	buf.erase();					//バッファ削除

	while (std::getline(EnemyCsv,buf))	//ファイルの終端まで読み込む
	{
		column = 0;
		std::stringstream ss(buf);	//ストリームにして扱いやすくする
		std::string tmp[9];			//一時保存用

		for (column; column < 9; column++)	//9個の要素があるのでその分ループ
		{
			int value;		//代入用

			std::getline(ss, tmp[column], ',');	//分割して
			value = std::atoi(tmp[column].c_str());	//変換して代入

			switch (column)	//要素で分けてみたり
			{
					//ひたすら数字を数値に変換しつつデータをブチ込む
				case 0: enemydata[row].ID = value; break;
				case 1: enemydata[row].x = value; break;
				case 2: enemydata[row].y = value; break;
				case 3: enemydata[row].in_time = value; break;
				case 4: enemydata[row].stop_time = value; break;
				case 5: enemydata[row].shot_time = value; break;
				case 6: enemydata[row].out_time = value; break;
				case 7: enemydata[row].move_kind = value; break;
				case 8: enemydata[row].shot_kind = value; break;
			}
		}
		row++;
	}

}

Manager::~Manager()
{
	delete player;
	delete board;
}
Enemy.hpp

コード:

#pragma once
#include <list>

class Enemy
{
		//敵
	float x,y;						//座標
	float speed;					//速さ
	float vx, vy;					//速度
	float ang;						//角度
	int gr[24];						//グラフィックハンドル
	int imgX,imgY;					//画像のサイズ
		//行動パターン制御用
	int in_time,stop_time,shot_time,out_time;
	int cnt;						//カウンタ
	int s_cnt;						//敵がいま打っている弾の数の保持用
	int shot_kind,move_kind;		//移動パターン、弾幕パターンの保持用
	bool endflag;					//敵が消滅したかのフラグ
	bool startflag;					//敵が生成されたかのフラグ

		//弾
	typedef struct
	{
		float x, y;		//座標
		float vx, vy;	//速度
		float ang;		//角度
		float speed;	//早さ
		float colrad;	//あたり判定の半径
		int kind;		//弾の種類
		int cnt;		//カウンタ
		bool flag;		//存在するかのフラグ
	}Shot;

	std::list<Shot> shot;
	int s_gr[16];

		//弾用パラメータ(使用するかどうかは任意)
	int s1;
	int s2;
	int s3;
	int s4;
	float sf1;
	float sf2;
	float sf3;
	float sf4;
	bool s_end;	//弾幕が終了したかのフラグ

	void Update();
		void Move();
		void Enter_shot();
		void Move_shot();

	void Draw();

public:
	bool All();
	bool IsDestroyed();
	bool IsStarting();
	bool Init(float arg_x, float arg_y, int arg_in_time, int arg_stop_time, int arg_shot_time, int arg_out_time, int arg_move_kind, int arg_shot_kind);
	Enemy();
	~Enemy();
};
Enemy.cpp

コード:

//敵のクラス。
//移動やショットなどの制御、描画等


#include "Enemy.hpp"
#include "GV.h"
#include "System.hpp"
#include <cmath>

Enemy::Enemy()
{
	startflag = false;
}

bool Enemy::IsDestroyed()
{
	return endflag;
}

bool Enemy::IsStarting()
{
	return startflag;
}

bool Enemy::Init(float arg_x, float arg_y, int arg_in_time, int arg_stop_time, int arg_shot_time, int arg_out_time, int arg_move_kind, int arg_shot_kind)
{
	LoadDivGraph("assets/img/char/Enemy.png", 24, 6, 4, 64, 64, gr, true);	//キャラ画像の読み込み
	GetGraphSize(gr[0], &imgX, &imgY);								//キャラのサイズを取得

																	//初期位置設定
	x = arg_x;
	y = arg_y;
	vx = 0.0f;
	vy = 0.0f;
	ang = 0.0f;

	//移動タイミング設定
	in_time = arg_in_time;
	stop_time = arg_stop_time;
	shot_time = arg_shot_time;
	out_time = arg_out_time;

	//カウンタ、フラグ、敵の種類を初期化
	move_kind = arg_move_kind;
	shot_kind = arg_shot_kind;
	cnt = 0;
	s_cnt = 0;
	endflag = false;
	startflag = true;

	LoadDivGraph("./assets/img/bullet/bullet01.png", 16, 4, 4, 64, 64, s_gr, true);



	//弾のパラメータ初期化
	s1 = 0;
	s2 = 0;
	s3 = 0;
	s4 = 0;
	sf1 = 0;
	sf2 = 0;
	sf3 = 0;
	sf4 = 0;
	s_end = false;
	return 0;
}
	
	//更新
void Enemy::Update()
{
	cnt++;
	s_cnt = 0;
	Move();
	Enter_shot();

		//敵が持つ弾が無く、かつ画面外に出たらこの敵を消すフラグを立てる
	if (120 < this->cnt && s_cnt == 0)
	{
		if (this->x + this->imgX < FIELD_X || FIELD_X + FIELDSIZE_X < this->x + this->imgX ||
			this->y + this->imgY < FIELD_Y || FIELD_Y + FIELDSIZE_Y < this->y + this->imgY)
			this->endflag = true;
	}
}

	//敵キャラ移動
void Enemy::Move()
{
	int gcnt = System::Instance()->GetCount();
	int gtime = System::Instance()->GetTime();
		//switch文により行動パターン分け
	switch (move_kind)
	{
		case 0:	//降りてきてそのまま帰る
		{
			this->speed = 5;
			if (gcnt < stop_time)
				this->ang = TRANS_RAD(90);
			else if (stop_time < gcnt && gcnt < out_time)
				this->speed = 0;
			else if (gcnt > out_time)
				this->ang = TRANS_RAD(270);
			break;
		}
		case 1:	//降りてきて右回転して帰る
		{
			this->speed = 5;
			if (gcnt < stop_time)
				this->ang = TRANS_RAD(90);
			else if (stop_time < gcnt && gcnt < out_time)
				this->ang = TRANS_RAD(-gcnt + stop_time + 90);
			else if (out_time < gcnt)
				this->ang = TRANS_RAD(-out_time+stop_time+90);

			break;
		}
		case 2:
		{
			this->speed = 3;
			if (this->cnt < 90)
			{
				this->ang = TRANS_RAD(90);
			}
			else if (90 < this->cnt && this->cnt < 720)
			{
				this->ang = TRANS_RAD(90 - (this->cnt - 90));
			}
			else if (720 <= this->cnt)
			{
				this->ang = TRANS_RAD(270);
			}
			break;
		}
		case 3:
		{
			break;
		}

		default:
		{
			printfDx("Error:move_kind %d is not exist", move_kind);
			break;
		}
	}
		//実際に移動させる
	this->vx = cos(this->ang)*this->speed;
	this->vy = sin(this->ang)*this->speed;
	this->x += this->vx;
	this->y += this->vy;

}

	//敵ショットの登録
void Enemy::Enter_shot()
{
	if (System::Instance()->GetCount() > shot_time && System::Instance()->GetCount() < out_time)	//発射開始以降に登録を開始
	{
		switch (shot_kind)	//弾幕の種類分け
		{
			case 0:	//全方位弾
			{
				const int way = 36;
				std::list<Shot> tmp;
				Shot t;
				if (this->cnt % 6 == 0)
				{
					for (int i = 0; i < way; i++)
					{
						t.ang = TRANS_RAD(i * 10 + this->cnt);
						t.x = this->x;
						t.y = this->y;
						t.cnt = 0;
						t.kind = 0;
						t.flag = true;
						t.speed = 8;
						t.colrad = 15;
						shot.push_back(t);
					}
				}
				break;
			}

			case 1:
			{
				break;
			}


			default:
				break;
		}
	}
		//実際に移動させる
	Move_shot();
}

	//敵ショットの移動
void Enemy::Move_shot()
{
	for (auto itr = shot.begin(); itr != shot.end();)
	{
		if (!(itr->x < FIELD_X || FIELD_X + FIELDSIZE_X < itr->x ||
			itr->y < FIELD_Y || FIELD_Y + FIELDSIZE_Y < itr->y))
		{
			s_cnt++;

			itr->vx = cos(itr->ang)*itr->speed;
			itr->vy = sin(itr->ang)*itr->speed;

			itr->x += itr->vx;
			itr->y += itr->vy;

			itr->cnt++;
			itr++;
		}
		else {
			itr = shot.erase(itr);
			s_cnt--;
		}
	}
}

	//敵キャラの描画
void Enemy::Draw()
{
		//敵キャラおよび敵ショットはゲーム画面内でのみ描画させる
	SetDrawArea(FIELD_X, FIELD_Y, FIELD_X + FIELDSIZE_X, FIELD_Y + FIELDSIZE_Y);
	for (auto itr = shot.begin(); itr != shot.end();)
	{
		DrawRotaGraph(itr->x, itr->y, 0.8f, itr->ang + 3.14159/2, s_gr[itr->kind], true, false);
		itr++;
	}


	int img=0;
	if (!endflag)
	{
		img = cnt%30/10;
		DrawRotaGraphF(x,y,2.0,0.0,gr[img],true,false);
	}

	SetDrawAreaFull();
	printfDx("%d Shots", s_cnt);
}

	//すべて呼ぶ関数
bool Enemy::All()
{
	Update();
	Draw();
	return true;
}

Enemy::~Enemy()
{

}

YuO
記事: 947
登録日時: 13年前
住所: 東京都世田谷区

Re: std::listをメンバにもつクラスを削除しようとするときに例外が投げられる問題

#2

投稿記事 by YuO » 6年前

例外を出すdeleteの上にある,明示的なデストラクタの呼び出しは何のために行っていますか。
明示的なデストラクタの呼び出しは,通常必要になることはありません。

あと,例外が発生するではなく,その例外の種類やメッセージも書いてください。

アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

Re: std::listをメンバにもつクラスを削除しようとするときに例外が投げられる問題

#3

投稿記事 by Hiragi(GKUTH) » 6年前

情報が不足していました、すいません。

例外のメッセージの全文を張ると

コード:

ハンドルされない例外が 0x00007FF6B3AC1827 (STG_Windows.exe) で発生しました: 0xC0000005: 場所 0xFFFFFFFFFFFFFFFF の読み取り中にアクセス違反が発生しました。
になります。

>明示的なデストラクタの呼び出しは,通常必要になることはありません。
知りませんでした、該当箇所から明示的な呼び出しを削除しました。

追記:例外の場所前後のソースを張り付けておきます。
list内

コード:


	iterator begin() _NOEXCEPT
		{	// return iterator for beginning of mutable sequence
		return (iterator(this->_Nextnode(this->_Myhead()), //ココ
			_STD addressof(this->_Get_data())));
		}


だいがくせい!

YuO
記事: 947
登録日時: 13年前
住所: 東京都世田谷区

Re: std::listをメンバにもつクラスを削除しようとするときに例外が投げられる問題

#4

投稿記事 by YuO » 6年前

さらに見てみましたが,deleteした後にそのオブジェクト(の残骸)に触ろうとしていますが,これは何故ですか。
deleteした後(明示的なデストラクタの呼び出しでも同じですが)に,そのオブジェクト(の残骸)へアクセスすることは未定義の振る舞いとなっています。
オフトピック
いくつかの例外はあります。ISO/IEC 14882:2011だと3.8の5段落など。
そもそも,Manager::enemyはEnemy * []ではなく,std::vector<Enemy>やstd::vector<std::shared_ptr<Enemy>>で十分ではないでしょうか。

アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

Re: std::listをメンバにもつクラスを削除しようとするときに例外が投げられる問題

#5

投稿記事 by Hiragi(GKUTH) » 6年前

YuO さんが書きました:さらに見てみましたが,deleteした後にそのオブジェクト(の残骸)に触ろうとしていますが,これは何故ですか。
deleteした後(明示的なデストラクタの呼び出しでも同じですが)に,そのオブジェクト(の残骸)へアクセスすることは未定義の振る舞いとなっています。
オフトピック
いくつかの例外はあります。ISO/IEC 14882:2011だと3.8の5段落など。
deleteした後のクラスに触ろうとしていますね...気づきませんでした。
YuO さんが書きました: そもそも,Manager::enemyはEnemy * []ではなく,std::vector<Enemy>やstd::vector<std::shared_ptr<Enemy>>で十分ではないでしょうか。
最初、弾の管理もすべて配列で行っていましたが、listでの管理に変えてきている状況です。とりあえず弾をlist管理にしようとしてその作業途中に今回の
問題が出てきましたので、質問させていただきました。Enemyもvectorでの管理にしてからまた報告します。
だいがくせい!

アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

Re: std::listをメンバにもつクラスを削除しようとするときに例外が投げられる問題

#6

投稿記事 by Hiragi(GKUTH) » 6年前

進展があったので報告しておきます。
Managerクラスの持つPlayer Board Enemyをすべてshared_ptrの管理にしました。結果、例外は発生しなくなりました。
かなり変わりましたが、以下に解決後のソースを提示します。

Manager.hpp

コード:

#include "Player.hpp"
#include "Enemy.hpp"
#include "Back.hpp"
#include <vector>
#include <memory>

class Manager
{
private:
	const int ENEMY_MAX = 16;

	std::shared_ptr<Board> board;
	std::shared_ptr<Player> player;
	std::vector<std::shared_ptr<Enemy>> enemy;

		//敵データ読み込み用
	typedef struct
	{
		int ID;
		float x;
		float y;
		int in_time;
		int stop_time;
		int shot_time;
		int out_time;
		int move_kind;
		int shot_kind;
	}EnemyData;

public:
	Manager();
	~Manager();
	void All();
	void LoadEnemyData();
};
Manager.cpp

コード:

//少なくともゲームのメインシーンにおいて各々を管理するためのクラスです。
//後々シーン管理なり始めたらなにかしらのクラスの継承先になる可能性があります。

#include "Manager.hpp"
#include "System.hpp"
#include <fstream>
#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <memory>

Manager::Manager()
{
	player = std::shared_ptr<Player>(new Player());
	board = std::shared_ptr<Board>(new Board());
	LoadEnemyData();
}

void Manager::All()
{
	int start = GetNowCount();
	board->All();
	player->All();

	for (auto itr = enemy.begin();itr != enemy.end();)
	{
		auto tmp = *itr;
		if(tmp->GetStartCount() < System::Instance()->GetCount())
			tmp->All();

		if (tmp->IsDestroyed())
			itr = enemy.erase(itr);
		else
			itr++;
	}
	printfDx("Game %d ms.\n", GetNowCount() - start);
}

	//クソ設計過ぎて泣ける
void Manager::LoadEnemyData()
{
	std::ifstream EnemyCsv("./assets/csv/Enemy00.csv");	//ファイル開く
	std::string buf;	//バッファ
	EnemyData data;
	int row = 0;	//行
	int column = 0;	//列
	
	std::getline(EnemyCsv, buf);	//一行読み飛ばして
	buf.erase();					//バッファ削除

	while (std::getline(EnemyCsv,buf))	//ファイルの終端まで読み込む
	{
		column = 0;
		std::stringstream ss(buf);	//ストリームにして扱いやすくする
		std::string tmp[9];			//一時保存用

		for (column; column < 9; column++)	//9個の要素があるのでその分ループ
		{
			int value;		//代入用

			std::getline(ss, tmp[column], ',');	//分割して
			value = std::atoi(tmp[column].c_str());	//変換して代入

			switch (column)	//要素で分けてみたり
			{
					//ひたすら数字を数値に変換しつつデータをブチ込む
				case 0: data.ID = value; break;
				case 1: data.x = (float)value; break;
				case 2: data.y = (float)value; break;
				case 3: data.in_time = value; break;
				case 4: data.stop_time = value; break;
				case 5: data.shot_time = value; break;
				case 6: data.out_time = value; break;
				case 7: data.move_kind = value; break;
				case 8: data.shot_kind = value; break;
			}
		}
			//vectorにブチ込む
		enemy.push_back(std::shared_ptr<Enemy>(new Enemy(data.x, data.y, data.in_time, data.stop_time, data.shot_time, data.out_time, data.move_kind, data.shot_kind)));
	}

}

Manager::~Manager()
{

}
だいがくせい!

返信

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