合計 昨日 今日

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

[このトピックは解決済みです]

フォーラムルール
フォーラムルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Name: Hiragi(GKUTH)
[URL]
熟練のプログラマー(55,184 ポイント)
Date: 2017年5月11日(木) 18:45
No: 1
(OFFLINE)

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

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

現在、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
コード[C++]: 全て選択
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#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
コード[C++]: 全て選択
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//少なくともゲームのメインシーンにおいて各々を管理するためのクラスです。
//後々シーン管理なり始めたらなにかしらのクラスの継承先になる可能性があります。
 
#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
コード[C++]: 全て選択
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#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
コード[C++]: 全て選択
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
//敵のクラス。
//移動やショットなどの制御、描画等
 
 
#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()
{
 
}

Name: YuO
[URL]
ハッカー(153,965 ポイント)
Date: 2017年5月11日(木) 21:22
No: 2
(OFFLINE)

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

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

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

Name: Hiragi(GKUTH)
[URL]
熟練のプログラマー(55,184 ポイント)
Date: 2017年5月11日(木) 22:08
No: 3
(OFFLINE)

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

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

例外のメッセージの全文を張ると
コード[Text]: 全て選択
1
ハンドルされない例外が 0x00007FF6B3AC1827 (STG_Windows.exe) で発生しました: 0xC0000005: 場所 0xFFFFFFFFFFFFFFFF の読み取り中にアクセス違反が発生しました。

になります。

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

追記:例外の場所前後のソースを張り付けておきます。
list内
コード[C++]: 全て選択
1
2
3
4
5
6
 
    iterator begin() _NOEXCEPT
        {   // return iterator for beginning of mutable sequence
        return (iterator(this->_Nextnode(this->_Myhead()), //ココ
            _STD addressof(this->_Get_data())));
        }

Name: YuO
[URL]
ハッカー(153,965 ポイント)
Date: 2017年5月11日(木) 22:20
No: 4
(OFFLINE)

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

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


そもそも,Manager::enemyはEnemy * []ではなく,std::vector<Enemy>やstd::vector<std::shared_ptr<Enemy>>で十分ではないでしょうか。

Name: Hiragi(GKUTH)
[URL]
熟練のプログラマー(55,184 ポイント)
Date: 2017年5月11日(木) 22:40
No: 5
(OFFLINE)

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

YuO さんが書きました:さらに見てみましたが,deleteした後にそのオブジェクト(の残骸)に触ろうとしていますが,これは何故ですか。
deleteした後(明示的なデストラクタの呼び出しでも同じですが)に,そのオブジェクト(の残骸)へアクセスすることは未定義の振る舞いとなっています。
Offtopic :
いくつかの例外はあります。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での管理にしてからまた報告します。

Name: Hiragi(GKUTH)
[URL]
熟練のプログラマー(55,184 ポイント)
Date: 2017年5月17日(水) 21:30
No: 6
(OFFLINE)

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

[解決!]

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

Manager.hpp
コード[C++]: 全て選択
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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
コード[C++]: 全て選択
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//少なくともゲームのメインシーンにおいて各々を管理するためのクラスです。
//後々シーン管理なり始めたらなにかしらのクラスの継承先になる可能性があります。
 
#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()
{
 
}


Return to C言語何でも質問掲示板

オンラインデータ

このフォーラムを閲覧中のユーザー: なし & ゲスト[14人]