vectorへのオブジェクト格納

[戻る]
Baby Chicken [HomePage] 2009/10/26(月) 07:33:04
皆様方ご無沙汰しております。初歩的な質問で申し訳ありません。
Vista-Ultimate, VC++2008です vectorでオブジェクトを格納するのに下記のようなのを
書きました。
#include <iostream>
#include <vector>
using namespace std;

class A {
    static int i;
    int _i;
public:
    A() {
        cout << i << " is born.\n";
        _i = i++;
    }
    ~A() {cout << "A is deletinng.\n";}
    void print() {cout << _i*_i << endl;}
};
int A::i = 0;

int main() {
    vector <A*> groupA;
    vector <A*>::iterator it;
    for (int i=0; i<10; ++i) {
        groupA.push_back(new A());
    }
    for (it = groupA.begin(); it != groupA.end(); ++it) {
        (*it)->print();
    }
                

    char ch;
    cin >> ch;
    return 0;
}

質問は
(1)実行すると、Aのdestructorが呼ばれていないようですが、vectorに格納する場合に
は、
        A *p = new A();
                groupA.push_back(p);
                delete p;
のように分けて書かないといけないのでしょうか? 
(2)VC++でメモリーリークを検出するのにはどのようにすれば良いのでしょうか?
ご指導宜しくお願いします。

tetrapod 2009/10/26(月) 08:19:47
まずそもそも vector (に限らず STL コンテナ) にポインタ値を入れるという設計が
あまりよくない (多態が必要な場合を除く) ので、その辺から再検討。

提示 (1) は
A* p=new A;
A* q=p;
delete p;
と同じことをやっているわけで delete p; の後はもう q を使うことができない。

(2) は、そもそもメモリーリークって何を意図してる?というあたりが明確でないので
答えにくいかな。

# 提示例 class A はバグっているというか、意味がよくわからないのでパスね

επιστημη [E-Mail] [HomePage] 2009/10/26(月) 09:22:31
> (1)実行すると、Aのdestructorが呼ばれていないようですが

呼ばれるワケがないので、mainの最後で明示的にdeleteします。

    for (vector <A*>::iterator it = groupA.begin(); it != groupA.end(); ++it) {
        delete *it;
    }

C++0X準拠(VC++2010)になれば vector<shared_ptr<A>> でしょね。

Baby Chicken [HomePage] 2009/10/26(月) 10:00:07
[[解決]]
皆様方ご教示ありがとうございます。感謝しています。

tetrapod様
STLコンテナにポインタを入れるのは良くない、というのは何となく分かっていましたが、
そうなのですね。ありがとうございます。class Aはただのdummyです
メモリーリークの検出については、以前VC++で別のもう少しきちんとしたプログラム
(bitmapを用いたりするもの)を書いた時、debug modeで走らせたところ、最後にdebug 
screenに memory leak was detectedと表示され、ポインタの解放忘れに気付いたのです
が、今回のものでは全く何も表示されず、プログラムが正しいように終了したからです。質
問が明確でなく申し訳ありません。

επιστημη様
やはり呼ばれる訳無いのですね。何となくvectorに期待していましたので・・・ そうです
ね、お示しされましたようにもう一度iteratorを使ってdeleteすれば良いのですね、こんな
ことも分からないなんて本当に馬鹿でした。お手を煩わせて申し訳ありませんでした。

仲澤@失業者 2009/10/26(月) 10:57:00
回答ではありませんが、一般に配列クラスに格納している
要素がnewしたオブジェクトであることが保障されている場合、
それ専用に派生させた配列クラスのデストラクタで全ての
配列要素ををdeleteするってのは常用してます。
以降、この配列クラスから派生させたクラスの要素を削除する
コードは不要になるので楽チン。
ただし、newしたオブジェクトのポインタの「コピー」の配列は
削除しちゃいけない場面が想定できるので注意が必要ですが(vv;)。

επιστημη [E-Mail] [HomePage] 2009/10/26(月) 12:24:33
↓VC9(2008)ならさっくり動きます。お試しあれ。

#include <iostream>
#include <vector>
#include <memory>

using namespace std;
namespace std { using namespace std::tr1; }

class A {
    static int i;
    int _i;
public:
    A() {
        cout << i << " is born.\n";
        _i = i++;
    }
    ~A() {cout << "A is deletinng.\n";}
    void print() {cout << _i*_i << endl;}
};

int A::i = 0;

int main() {
    vector<shared_ptr<A>> groupA;
    for (int i=0; i<10; ++i) {
        groupA.push_back(shared_ptr<A>(new A()));
    }
    for (vector<shared_ptr<A>>::iterator it = groupA.begin(); it != groupA.end
(); ++it) {
        (*it)->print();
    }
}

Baby Chicken [HomePage] 2009/10/26(月) 14:31:48
仲澤@失業者様
そうか、配列クラスを作ってそこのdestructorの中でdeleteするというのも(作るのは難し
そうですが)なかなか魅力的な気がします。今度挑戦してみます。

επιστημη様
shared_prtを用いてdeleteせずに済ます方法ご教授ありがとうございます。

仲澤@失業者 2009/10/26(月) 17:20:48
> そうか、配列クラスを作ってそこのdestructorの中でdeleteするというのも(作るのは
難し
> そうですが)なかなか魅力的な気がします。今度挑戦してみます。

そんなに難しくないですよ。・・・テンプレート使えば。
stlはうっとおしいので、MFCのCArryから派生させると

template< typename T >
class ARY_with_AutoDel
: public CArray< T*>
{
private:     BOOL m_AutoDel;    // 自動削除
public:
  ARY_with_AutoDel() : CArray< T*>(){
    m_AutoDel    = TRUE;
  }
public:
  virtual ~ARY_with_AutoDel(){ Destroy();}
  void AutoDelete_Set( BOOL ex_flg){ m_AutoDel=ex_flg;}
  void Destroy(){
   if( FALSE == m_AutoDel){
       RemoveAll();
       return;
   }
   int num = int( this->GetCount());    // 要素数
     T*    t;
     for( int i = num - 1 ; i>= 0 ; i--){
        t = ( *this)[ i];
        if( t)    delete t;
        ( *this)[ i] = NULL;
     }
     RemoveAll();
   }
};

class HOGE{
 public:HOGE(){}
        virtual ~HOGE(){} // virtualである必要がある    
};
だったとき、HOGEの自動削除配列は

typedef ARY_with_AutoDel<HOGE>  HOGE_ARY; // 配列クラスの定義
HOGE_ARY  HogeAry;       // 配列のインスタンス
             // 配列に要素を追加
HogeAry.Add( new HOGE);  // このobjは配列破棄時に自動的にdeleteされる

てな感じ。簡単ですよねぇ。えと、ソース検証してないのでよろしく。
ちなみに適切に使ってる分にはHOGEの子孫群のコンポジットな配列でも、
ちゃんとdeleteしてくれます(デストラクタがvirtualは必須)。

Baby Chicken [HomePage] 2009/10/28(水) 09:39:45
仲澤@失業者様

ご指導ありがとうございます。とっても難しく感じるのですが、理解できるように頑張りま
す。

とおりすがり 2009/10/28(水) 11:07:24
全然違う話ですみませんが、STLにポインタを入れるのがよろしくないのは
なぜでしょう?自分はとてもよく使っています。
理由としてはOperator=の実装が要らないからだったり特定画面のWndハンドルだったり
するからなのですが・・・

επιστημη [E-Mail] [HomePage] 2009/10/28(水) 11:48:26
僕もそんなに忌み嫌うほどのこっちゃない思います。
問題は追加/削除および共有が起こったときにややこしくなるってとこですから。

とおりすがり 2009/10/28(水) 13:11:51
共有とかはクラスにコンテナ押し込めてconst返したり
シングルトンとか使いますねぇ…
ネットワークでどうなるかはやったことないからわかりませんが

PATIO 2009/10/28(水) 13:28:38
共有が起こった時にそれをちゃんと認識した上で管理していないと
削除した時に問題が怒ると言う部分が一番面倒でしょうねぇ。
STLから直接参照している分には良いのでしょうけれど、
STL内に格納されているポインタが別の場所に一人歩きしてしまうと
STLから削除する時にうかつに削除できなくなります。
その部分が一番問題なんじゃないかと思います。

きっちりかっちり管理されていれば問題なさそうですけどね。
もしくはsharedptrで被せてSTLに突っ込むのが一番無難そう。

PATIO 2009/10/28(水) 13:30:15
うわー。

誤)
削除した時に問題が怒ると言う部分が一番面倒でしょうねぇ。

正)
削除した時に問題が起こると言う部分が一番面倒でしょうねぇ。

まあ、使っているところを勝手に削除されたら怒るでしょうけれど。(^^;

maru 2009/10/28(水) 14:50:00
> STLにポインタを入れるのがよろしくないのは
> なぜでしょう?
ポインタはバグの宝庫。なので、
STLはコンテナにオブジェクトそのものを格納出来るのに、わざわざバグが発生しやすい
ポインタを使うことはないんじゃないの?くらいでは?

> sharedptrで被せてSTLに突っ込むのが一番無難そう。
御意。

ZCH 2009/10/28(水) 20:45:21
脱線ついでに、、

> ポインタはバグの宝庫。なので、

こう片付けるのはどうかと。。
そもそもポインタがなんたるか、STLでどう管理しているかを
理解していれば、そもそもバグなんて出ないですし、
(1)のようなコードも書かないと思います。

クラスの構造から逆にポインタで管理した方がコードが
シンプルで見やすい・保守しやすいなんてこともあるかと。

επιστημη [E-Mail] [HomePage] 2009/10/28(水) 23:11:29
ポインタはバグの巣であることは否定しないけども
かといって一掃することもできんのだから、
うまく折り合いをつけるが肝要かと。
バグを抑え込みつつ利用するひとつの手段がshared_ptrってーことで。

rin 2009/10/29(木) 09:38:09
ポインタはバグの宝庫。
ポインタはバグの巣

という表現されてますが
ポインタという技術・考え方といったそのものに、
いかんともしがたいバグがあるのでしょうか?

そうすると、どういったバグなのでしょうか?

maru 2009/10/29(木) 10:13:01
脱線が続くので、別スレッドを立てた方がよいのかも?

> 理解していれば、そもそもバグなんて出ないですし、
理解していても人間は間違えるものなので、バグは発生します。

> まずそもそも vector (に限らず STL コンテナ) にポインタ値を入れるという設計が
> あまりよくない (多態が必要な場合を除く) ので、その辺から再検討。
はtetrapodさんが理由をいってくれないと真意は分かりませんけどね。

私はSTLコンテナに(多態が必要な場合を除いて)ポインタ値を入れるのを「絶対」禁止
とは思っていませんが、バグが少なくなるならそれはその方がいいわけで、
επιστημηさんの
> かといって一掃することもできんのだから、
> うまく折り合いをつけるが肝要かと。
という意見に賛成です。

> そうすると、どういったバグなのでしょうか?
領域の解放忘れ(メモリリーク)
解放済みメモリへのポインタ(ダングリングポインタ)のアクセス
確保した領域を超えたアクセス
等々

tetrapod 2009/10/29(木) 11:15:49
しばらく見ないうちに盛り上がっている・・・呼んだ?
あくまでも俺個人の意見として maru 2009/10/28(水) 14:50:00 とほぼ同意。

代入可能やコピーコンストラクト可能でないクラスを STL コンテナに入れるのは困難
であり、STL コンテナに入るのはあくまでも元オブジェクトのコピー。
コピー=新しい実体である、ということ。
RAII 的観点からも STL コンテナには実体が入っているほうが扱いやすいよね。
コンテナの消滅=管理しているオブジェクトの実体の消滅になるわけで。

生ポインタ値をコンテナに入れたら、ポインタ値のコピーが作られるだけなので
ポインタが指す先の実体のコピーはなされない。
コンテナ(とそれに入っているポインタ値)があっても、それが指し示すべき実体が
ないという状況が安易に作れてしまいバグの元だ。と思う。
コンテナを消滅させても実体が残りうるのはバグの元だと思う。

生ポインタ値を入れるのは推奨しないとは書いたが shared_ptr<T> を入れることまで
ダメとは書いた覚えがない。設計上、コンテナにポインタを入れる必然があるなら
諸氏と同様、俺も shared_ptr<T> を使うのを推奨する。

rin 氏
およそコンピュータというものは間違わないわけよ。
「プログラムに書かれたとおりに動作する」という点で。
間違うのは人間。

ポインタそのものにバグはなく、ポインタを使う人間のほうが誤りやすいだけ。
ならば人間の誤りを可能な限り減らすように最初から心がけるほうがいい。
vector<T*> より vector<T> のほうがバグらせにくい、
vector<T*> より vector<shared_ptr<T> > のほうがバグらせにくい、
んではないかな、と俺は思う。

maru 2009/10/29(木) 11:41:03
> しばらく見ないうちに盛り上がっている・・・呼んだ?
はい。

tetrapodさんの
> rin 氏
> およそコンピュータというものは間違わないわけよ。
> 「プログラムに書かれたとおりに動作する」という点で。
> 間違うのは人間。
> ポインタそのものにバグはなく、ポインタを使う人間のほうが誤りやすいだけ。
をみて、自分がrinさんの質問を読み違えていたことに気付きました。
「ポインタという技術・考え方といったそのもの」にバグがあるわけではないんですよ
ね。
なので、
> 領域の解放忘れ(メモリリーク)
> 解放済みメモリへのポインタ(ダングリングポインタ)のアクセス
> 確保した領域を超えたアクセス
> 等々
は質問の回答になっていません。撤回します。

tetrapod 2009/10/29(木) 12:39:14
その例はある意味 rin 氏の質問に対する回答の一例ではあるわけだけど

ポインタという技術なり考え方なりはきわめて「機械にとって単純」なものなので、
それ自体がバグなり不具合なりを抱え込んでいるわけではない。
「機械にとって簡単」≠「人間にとって簡単」ということ。

> 領域の解放忘れ(メモリリーク)
> 解放済みメモリへのポインタ(ダングリングポインタ)のアクセス
> 確保した領域を超えたアクセス
というのは人間側の間違いかたの例ということで。

古いスレッドには追加発言できません。