ページ 11

関数にアクセスできない例外

Posted: 2018年2月08日(木) 20:11
by にほ
現在、龍神録の館、龍神録2の館、新ゲームプログラミングの館を参考にシューティングゲームを作っています。そこで、ウィンドウ内にオブジェクトを表示させようと思ってコードをかいたのですが、関数にアクセスできず例外が発生してしまいます。

コード:

// SceneGame.h

#pragma once

#include <vector>
#include <memory>

#include "Object.h"
#include "BaseScene.h"

class SceneGame : public BaseScene {
	vector<shared_ptr<Object>> obj;
	int stage;
public:
	SceneGame(ISceneChanger * ch, const Parameter& parameter);
	~SceneGame();
	void Update();
	void Draw() const;
};

コード:

// SceneGame.cpp

#include <DxLib.h>

#include "Pad.h"
#include "Define.h"
#include "SceneGame.h"
#include "Frame.h"
#include "Field.h"

SceneGame::SceneGame(ISceneChanger *ch, const Parameter& parameter) : BaseScene(ch, parameter) {
	float arrx[4] = { 0, 0, 0, 416 };
	float arry[4] = { 0, 16, 464, 0 };

	for (int i = 0; i < 4; i++) {
		// 枠オブジェクトを生成
		obj.push_back(make_shared<Frame>(i, arrx[i], arry[i]));
	}
	// フィールドを生成
	// この一文を書き足すとエラーが出るようになる
	obj.push_back(make_shared<Field>((float)Define::FX, (float)Define::FY));
	// 描画優先度によってソート
	sort(obj.begin(), obj.end());
}

SceneGame::~SceneGame() {
	for (unsigned i = 0; i < obj.size(); i++) {
		obj[i].reset();
	}
	obj.clear();
}

void SceneGame::Update() {
	for (unsigned i = 0; i < obj.size(); i++) {
		obj[i]->Update();
	}
	if (Pad::get(PAD_START) == 1) {
		Parameter parameter;
		changer->ChangeScene(Scene_Title, parameter, TRUE);
	}
}

void SceneGame::Draw() const {
	for (unsigned i = 0; i < obj.size(); i++) {
		obj[i]->Draw(); // ここでエラーが起きる
	}
}

コード:

// Object.h

#pragma once

#include <memory>

using namespace std;

class Object {
protected:
	float x, y;
	int priority = 0;
public:
	Object();
	Object(float _x, float _y);
	~Object() = default;
	virtual void Update() = 0;
	virtual void Draw() const = 0;
	bool operator() (const shared_ptr<Object> left, const shared_ptr<Object> right) const {
		return left->priority < right->priority;
	}
	void setPos(float _x, float _y);
	void addPos(float _x, float _y);
	float getX();
	float getY();
};

コード:

// Field.h

#pragma once

#include <vector>
#include <memory>

#include "Object.h"

using namespace std;

class Field : public Object {
	vector<shared_ptr<Object>> obj;
	int i;
public:
	Field() = default;
	Field(float _x, float _y);
	~Field() = default;
	void Update();
	void Draw() const;
};
このようなコードを実行すると、数回は成功するのですが、途中で以下のような例外が発生します。
std::vector<std::shared_ptr<Object>,std::allocator<std::shared_ptr<Object> > >::operator[](...)._Ptr-> が 0xB5B8 でした。

最初の数回は期待通りの実行結果が得られるので、関数自体にアクセスすることは可能なのですが、その後アクセスできなくなっているようです。原因がわかる方、どなたかご教授いただければ幸いです。

Re: 関数にアクセスできない例外

Posted: 2018年2月09日(金) 18:10
by かずま
にほ さんが書きました: このようなコードを実行すると、数回は成功するのですが、途中で以下のような例外が発生します。
std::vector<std::shared_ptr<Object>,std::allocator<std::shared_ptr<Object> > >::operator[](...)._Ptr-> が 0xB5B8 でした。

最初の数回は期待通りの実行結果が得られるので、関数自体にアクセスすることは可能なのですが、その後アクセスできなくなっているようです。原因がわかる方、どなたかご教授いただければ幸いです。
Frame::Update や Field::Update がどうなっているのかの記述がないので、
SceneGame::obj がどんなふうに破壊されているのかが分かりません。

提示されたコードから分かることは、
SceneGame のコンストラクタの sort が間違っていることです。

sort(obj.begin(), obj.end()); で obj をソートしようとしていますが、
obj は shared_ptr の vector なので、ポインタの大小でソートされます。

ポインタが指す Object (Frame または Field) を priority でソートしたいらしくて、
Object の中で operator() を定義していますが、それは使われません。

次のように書けば、Object::operator() が呼び出されて、
priority でソートされます。

コード:

    sort(obj.begin(), obj.end(),
        [](shared_ptr<Object>& a, shared_ptr<Object>& b) { return (*a)(a, b); }
    );
でも、operator() が Object のメンバ関数というのは変ですよね。
メンバ関数は、メンバ変数を参照したり変更するためにあります。
自身のメンバ変数は参照せず、引数で与えられた 2つのオブジェクトの比較を
だけを行うとはどういうことでしょうか?
どうしてもこのメンバ関数を使うのならということで、(*a)(a, b) のようにしました。

また Object::operator()関数の引数の型を const shared_ptr<Object> にして
いますね。これだと、呼び出し時に shared_ptr のコピーが起こり、
shared_ptr の持っている参照カウントが 1 増えます。
関数を出るときは、参照カウントが 1 減ります。これらの余計な処理を
しないで済むように、参照 const shared_ptr<Object>& にしましょう。

メンバ関数で実現しようとすれば、

コード:

    bool operator() (const shared_ptr<Object>& right) const {
        return this->priority < right->priority;
    }

コード:

    sort(obj.begin(), obj.end(),
        [](shared_ptr<Object>& a, shared_ptr<Object>& b) { return (*a)(b); }
    );
または、

コード:

    bool operator<(const Object& right) const {
        return tpriority < right.priority;
    }

コード:

    sort(obj.begin(), obj.end(),
        [](shared_ptr<Object>& a, shared_ptr<Object>& b) { return *a < *b; }
    );
外部関数にしてしまってもいいでしょう。

コード:

bool lt(const shared_ptr<Object>& left, const shared_ptr<Object>& right) {
	return left->priority < right->priority;
}

コード:

	sort(obj.begin(), obj.end(),
		[](shared_ptr<Object>& a, shared_ptr<Object>& b) { return lt(a, b); }
	);
ただし、protectd の priority にアクセスできないので、class Object の中に
friend bool lt(const shared_ptr<Object>& left, const shared_ptr<Object>& right);
が必要です。

Re: 関数にアクセスできない例外

Posted: 2018年2月09日(金) 18:18
by かずま
かずま さんが書きました: 外部関数にしてしまってもいいでしょう。

コード:

bool lt(const shared_ptr<Object>& left, const shared_ptr<Object>& right) {
	return left->priority < right->priority;
}

コード:

	sort(obj.begin(), obj.end(),
		[](shared_ptr<Object>& a, shared_ptr<Object>& b) { return lt(a, b); }
	);
外部関数を用意したら、ラムダ式は不要でした。

コード:

	sort(obj.begin(), obj.end(), lt);

Re: 関数にアクセスできない例外

Posted: 2018年2月10日(土) 01:00
by にほ
返信ありがとうございます。sortの件、ありがとうございました。すべての文をコメントアウトした状態でも同様のエラーが発生したため、前の投稿には記述しておりませんでしたが、FieldとFrame内のコードということでしたので、以下に記します。

コード:

// Frame.cpp

#include <DxLib.h>

#include "Frame.h"
#include "Resource.h"

Frame::Frame(int _id) : id((_id >= 0 && _id < 4) ? _id : 0) {
	priority = 0;
}

Frame::Frame(int _id, float _x, float _y) : Object(_x, _y) , id((_id >= 0 && _id < 4) ? _id : 0) {
	priority = 0;
}

void Frame::setId(int _id) {
	id = (_id >= 0 && _id < 4) ? _id : 0;
}

int Frame::getId() {
	return id;
}

void Frame::Update() {

}

void Frame::Draw() const {
	// 枠画像を表示
	DrawGraphF(x, y, Image::getBoard(id), TRUE);
}

コード:

// Field.cpp

#include <DxLib.h>

#include "Field.h"
#include "Define.h"

Field::Field(float _x, float _y) : Object(_x, _y) {
	priority = 11;
}

void Field::Update() {
	for (unsigned i = 0; i < obj.size(); i++) {
		obj[i]->Update();
	}
}

void Field::Draw() const {
	// 仮の四角表示
	DrawBox(x, y, x + Define::FMX + 90, y + Define::FMY, 0x00ffff, TRUE);
	for (unsigned i = 0; i < obj.size(); i++) {
		// フィールド内のオブジェクト(プレイヤーや弾など)を描画
		obj[i]->Draw();
	}
}

Re: 関数にアクセスできない例外

Posted: 2018年2月10日(土) 02:52
by にほ
先ほど試したのですが、FieldクラスとGameSceneクラスのshared_ptrをunique_ptrに変更したところ、今のところエラーは置きなくなりました。どこかで予期せぬ参照が発生し、インスタンスが正しく破棄されていなかったのでしょうか。
一応このままunique_ptrを使ってコーディングしていきますが、エラーが完全になくなったわけではないのと納得のいく原因がわかっていないので解決済みにはせず、皆様のご意見をお伺いしたいと思います。

Re: 関数にアクセスできない例外

Posted: 2018年2月11日(日) 03:26
by にほ
あれから何度か試行錯誤して分かったことがあります。決まってobj[2]が破壊されるという事です(確定というわけではなく、他が確認できていない状態です)。priorityを変更してFieldクラスが先頭に並ぶようにしても同様の結果になりました。この時sortをしているかどうかにもかかわらず、破壊される要素は同じ場所です。
また、その時のデータが以下のようになっていました。

obj[2] shared_ptr {x=0.000000000 y=6.236e-39#DEN priority=33554432 } [1 strong ref] [make_shared]

xの値は正常ですが、yとpriorityの値が破壊されていることが分かりました。

Re: 関数にアクセスできない例外

Posted: 2018年2月12日(月) 08:04
by かずま
unique_ptr に変更すると、エラーが起きなくなるとか、
obj[2] の y と priority が破壊されるとか、
情報を小出しにされても、問題の解決には程遠いように思います。

すべての文をコメントアウトした状態でも同様のエラーが発生する
のなら、ソースは短くなるはずです。
それ全体を貼り付けることはできないでしょうか?

「論よりソース」です。