ページ 11

リストでつまづいています

Posted: 2010年8月27日(金) 23:20
by オーシロ
C言語でリストを学んだので、C++のクラスを使ってそれっぽく作れないかと思い、
以下の様なプログラムを組みました。

#include <stdio.h>

//基本データ。ここを改造してキャラクターの基本クラスを作る
class Data{
public:
    //暫定的に配置するデータ。1つ前のノードより1大きい数が入る
    int i;

    //コンストラクタ
    Data(int j){
        i = j + 1;
    }
    //デストラクタ
    ~Data(){
        printf("  %dDataを削除します\n", i);
    }

    //iの中身を確認する
    void nprint(){
        printf("     %dDataの内容は%d\n", i, i);
    }
};

//リストの1つ1つのデータ
class Node{
public:
    //データのアドレス
    Data* here;
    //1つ前のノードのアドレス
    Node* prev;
    //1つ先のノードのアドレス
    Node* next;

    //コンストラクタ。指定されたとおりにくっつけます
    Node(Data* h, Node* p, Node* n){
        here = h;
        prev = p;
        next = n;
    }
    //デストラクタ。
    ~Node(){
        printf(" %dNodeを削除します\n", here->i);
        delete here;
        prev->next = next;
        next->prev = prev;
    }
};

//リストそのもの。
class List{
public:
    //first。先頭。
    Node* fst;
    //ダミー
    Node* dummy;
    //要素数。
    int num;

    //コンストラクタ
    List(){
        //ダミーの実体?生成
        dummy = new Node(new Data(-1), NULL, NULL);
        //先頭のアドレスをダミーに。
        fst = dummy;
        //要素数は0に。(ダミーは数えない)
        num = 0;
    }
    //デストラクタ
    ~List(){
        //削除するアドレスを2番目にセッティング
        Node* dcur = fst->next;
        
        printf("listを削除します\n");

        //削除部分。入れ替えなんかの部分はNodeクラスの
        //デストラクタで済ませている…つもり
        while(dcur){
            Node* next = dcur->next;
            delete dcur;
            dcur = next;
        }
    }

    //要素追加
    void add(){
        //先端まで移動
        Node* l = fst;
        while(l->next != NULL) l = l->next;
        
        //先端に新しいデータをくっつける
        l->next = new Node(new Data(l->here->i), l, NULL);
        //要素数を加える
        num++;
    }
    //要素削除
    void dlt(int n){
        //指定された場所まで移動
        Node* l = fst;
        if(n <= 0 || n > num) return;
        for(;n!=0;n--){
            if(l-> next == NULL) return;
            l = l->next;
        }
        //その場所を削除
        delete l;
        //要素数を減らす
        num--;
    }
    //中身の参照
    void move(){
        Node* l = fst->next;
        while(l != NULL){
            l->here->nprint();
            l = l->next;
        }
    }
};

int main(){
    List lst;
    lst.add();
    lst.add();
    lst.add();
    lst.add();
    lst.add();
    lst.dlt(3);
    lst.move();
    return 0;
}

その結果は

 3Nodeを削除します
  3Dataを削除します
     1Dataの内容は1
     2Dataの内容は2
     4Dataの内容は4
     5Dataの内容は5
listを削除します
 1Nodeを削除します
  1Dataを削除します
 2Nodeを削除します
  2Dataを削除します
 4Nodeを削除します
  4Dataを削除します
 5Nodeを削除します
  5Dataを削除します

としっかり出たのですが、その後何故か強制終了されてしまいました。

原因はNodeクラスのデストラクタあたりにありそうなのですが、どのように解決すればよいかがわかりません。
何かヒントをもらえないでしょうか?

Re:リストでつまづいています

Posted: 2010年8月27日(金) 23:24
by ookami
コピーコンストラクタが見当たらないのは、関係ありますかね?

Re:リストでつまづいています

Posted: 2010年8月27日(金) 23:51
by オーシロ
>>ookamiさん

コピーコンストラクタという存在をまず知りませんでした。
まだ勉強が足りていませんね…すみません。

というわけで、
//コピーコンストラクタ
List(const List& obj){
    dummy = new Node(NULL, NULL, NULL);
    *dummy = *obj.dummy;
}
というのを追加してみたのですが、また強制終了されてしまいました。

となると、dummy = new Node(new Data(-1), NULL, NULL);という部分の、
「new Data(-1)」という部分が強制終了を引き起こしている元なのだと思うのですが、
ここをどうすれば良いのかがいまいちよく分かりません…。

Re:リストでつまづいています

Posted: 2010年8月27日(金) 23:52
by h2so5
とりあえずヒントを。

お察しの通り、Nodeクラスのデストラクタに問題があります。

オーシロさんのコードでは、削除されるNodeに必ず次のNodeがあることが前提になっています。
削除されるNodeがリストの一番最後、つまり次のNodeがない場合が考慮されていません。

Re:リストでつまづいています

Posted: 2010年8月28日(土) 00:01
by ookami
「コピーコンストラクタ」と「operator=」は、自分で定義しないとコンパイラが勝手に
適当なものを作ってくれてしまうため、
特にメンバにポインタがある場合には、予期しない動きをすることが多いです。

ただ、今回のケースでそれが原因なのか、実は私、確証なかったりします。
どう見ても知ったかぶりです。本当にry

Re:リストでつまづいています

Posted: 2010年8月28日(土) 00:08
by オーシロ
>>h2so5さん

なるほど、nextがNULLになっているNodeにも参照していたから強制終了が起こったわけですね。

というわけで、以下のように変更してみたところ、
    //デストラクタ。
    ~Node(){
        printf(" %dNodeを削除します\n", here->i);
        delete here;
        if(next != NULL){
            prev->next = next;
            next->prev = prev;
        }
        else prev->next = NULL;
    }
無事、強制終了されることなくプログラムを終了させることができました!


>>ookamiさん

今回はどうもそれとは違う部分が強制終了させていたようです。
ですが、とても参考になりました。もっと勉強しなくてはいけないと感じました。
ありがとうございました。


ookamiさん、h2so5さん、ありがとうございました!

Re:リストでつまづいています

Posted: 2010年8月28日(土) 02:07
by けえぼお
~Node() の prev->next = next; にも問題があると思います。(そのままでも強制終了はされませんが。)
~List() の delete dcur; で既に削除された要素を参照してます。

自分がリストクラスを作ってみたときは、Listクラス内に
prev->next = next;
next->prev = prev;
に当たる内容を書いたような気がします。

素人の意見ですがご参考までに。。。

Re:リストでつまづいています

Posted: 2010年8月28日(土) 06:06
by gyz
解決とされたようですが
コードを見て気づいた点があったので
レスさせていただきます。

Listクラスのデストラクタでfst(dummy)をdeleteしてないので
このままではメモリリークが発生します
デストラクタでちゃんとdeleteするか
fst(dummy)をNode*ではなくNodeにする必要があると思います

あと、Listクラスのdummyが意味なくないですか?
dummy = new Nodeして、そのアドレスをそのままfstに渡してるので
結局fst = new Nodeするのと変わらない処理になってると思います
dummy->fst->それ以降のリスト...というのを作る意図なら

dummy = new Node(略);
fst = new Node(new Data(dummy->here->i), &dummy, NULL);
dummy->next = fst;

こんな感じになるんではないかと思います