2009-02-20 11 views
19

私はstd :: map :: clear()の奇妙な動作を観察しています。このメソッドは、呼び出されたときに要素のデストラクタを呼び出すことになっていますが、clear()の呼び出し後もメモリにアクセスできます。例えばstd :: map :: clear()が呼び出された後にメモリがまだアクセス可能なのはなぜですか?

struct A 
{ 
    ~A() { x = 0; } 
    int x; 
}; 

int main(void) 
{ 
    std::map< int, A * > my_map; 
    A *a = new A(); 
    a->x = 5; 
    my_map.insert(std::make_pair< int, *A >(0, a)); 

    // addresses will be the same, will print 5 
    std::cout << a << " " << my_map[0] << " " << my_map[0]->x << std::endl; 

    my_map.clear(); 

    // will be 0 
    std::cout << a->x << std::endl; 

    return 0; 
} 

疑問は、なぜ変数ですaそのデストラクタがマップ::明らかで呼ばれた後にまだアクセス()? my_map.clear()に電話した後にdelete a;と書き込む必要がありますか、またはaの内容を上書きすることは安全ですか?マップは「doesnのため、

あなたの助けを事前に感謝し、 SNEG

+0

アクセスするにはat()を使用してみてください。例ではないものにはアクセスできません:http://ideone.com/ZeRTnd(以前はキー "B"の項目が定義されていましたが、map.clear )キー "B"にアクセスしようとすると '' std :: out_of_range''例外が発生する –

答えて

19

std :: mapはポインタ値によってポイントされるメモリを管理しません。あなた自身で行うことはあなた次第です。あなたはスマートポインタを使用したくない場合は、このような汎用無料&クリア機能書き込むことができます。

template <typename M> void FreeClear(M & amap) 
    for (typename M::iterator it = amap.begin(); it != amap.end(); ++it) { 
     delete it->second; 
    } 
    amap.clear(); 
} 

をし、それを使用します。

std::map< int, A * > my_map; 
// populate 
FreeClear(my_map) 

を。

+1

スマートポインタやboost :: shared_ptrに関する質問へのリンクを追加することをお勧めします。 –

19

あなたがマップ(またはリスト、あるいはそのようなもの)にポインタを格納した場合YOUはは、ポインタを削除する責任があります彼らが新しいもので作成されたものかどうかはわかりません。 clear関数は、ポインタを使用しない場合にのみデストラクタを呼び出します。

ああ、もう1つ:デストラクタを呼び出す(またはdeleteを呼び出す)ことは、メモリにもうアクセスできないことを意味しません。もしあなたがそうしたら、あなたはゴミにアクセスします。

+5

ガベージにアクセスしているかもしれないが、時にはガベージになっていない領域にアクセスすることもあるので、 –

4

これは、map.clear()が、aへのポインタのマップに含まれているデータのデストラクタを呼び出しているからです。そして、これは何もしません。

aが占有するメモリが自動的に回収されるように、ある種のsmart pointerをマップに配置したい場合があります。

ところで、なぜあなたはmake_pairへの呼び出しでテンプレート引数を入れますか?テンプレート引数の控除は、ここでかなりうまくいくはずです。

+0

スマートポインタクラスを使用していない場合は、マップを通過して手動で 'delete'を実行する必要がありますか?すべての要素を削除するにはどうすればいいですか?つまり、 'clear()'はすでに削除された要素のデストラクタを呼び出そうとします。 – sneg

+1

すべてのポインタを削除してからclear()を呼び出します。あなたに言った、デストラクタを呼び出す(それが指しているオブジェクトのデコンストラクタを呼び出すのと全く違う)ポインタは何もしません。 – Marc

+0

実際のオブジェクトをマップに格納すると、そのデストラクタが呼び出されます。しかし、ポインタ(あらゆる種類のポインタ)を格納すると、マップには無意味なポインタがたくさんあることがわかり、それらは基本的にはメモリアドレスを表す整数として扱われます。 – Marc

1

ヒープメモリを解放すると、その内容はゼロになりません。それらは再び割り当てのためだけに利用可能です。もちろん、割り当てられていないメモリへのアクセスの影響は未定義であるため、メモリにアクセスできないと考えるべきです。

実際にはメモリページへのアクセスを禁止するのは下位レベルで行われ、stdライブラリはそれを行いません。

新しいメモリを割り当てるときは、スマートポインタを使用しない限り、自分で削除する必要があります。あなたはそれがタイプに基づいヴァルを構築してからリンクすることによって起こる割り当てると

__NodePtr 
{ 
    *next; 
    __Ty Val; 
} 

0

任意のコンテナを格納し、オブジェクトの種類と呼び、対応するコンストラクタが:内部コードの各ノードは次のようになります。次のようなもの:

_Ty _Val = _Ty(); 
_Myhead = _Buynode(); 
_Construct_n(_Count, _Val); 

これを削除すると、対応するデストラクタが呼び出されます。

参照(ポインタ)を保存するときにコンストラクタを呼び出すことも、破壊することもありません。

1

食べて、寝る、そして呼吸する地図を最後の2ヶ月間使ったので、私はお勧めします。可能であれば、地図に自分のデータを割り当てさせる。それはあなたがここで強調しているのとまったく同じ理由で、よりクリーンです。

ファイルやソケットからマップのデータにデータをコピーする場合など、ノードが存在するとすぐにデータストレージが存在します。これは、マップがmalloc()を呼び出してノードを割り当てるとキーとデータの両方にメモリを割り当てます。

これにより、memcpy()の代わりに代入演算子を使用することができ、malloc()を1回呼び出す必要がありません。

IC_CDR CDR, *pThisCDRLeafData; // a large struct{} 

    while(1 == fread(CDR, sizeof(CDR), 1, fp)) { 
    if(feof(fp)) { 
     printf("\nfread() failure in %s at line %i", __FILE__, __LINE__); 
    } 
    cdrMap[CDR.iGUID] = CDR; // no need for a malloc() and memcpy() here  
    pThisCDRLeafData = &cdrMap[CDR.iGUID]; // pointer to tree node's data 

ここで指摘する価値のある注意点がいくつかあります。

  1. malloc関数にマップの呼び出し()を保持する場所を割り当てられた前のポインタを返します)はmalloc(にお電話としてツリーノードを追加するコードの行でのmalloc()、または新規を呼び出すことはありませんあなたのmallocから戻る()。
  2. デバッグモードでは、メモリを解放()しようとしても同様の問題が発生することが予想されます。どちらもコンパイラの問題のようですが、少なくともMSVC 2012では存在しており、重大な問題です。
  3. あなたのマップを「アンカー」する場所についていくつか考えてください。 IE:宣言されている場所。あなたは間違って範囲外に出ることを望んでいません。 main {}は常に安全です。

    INT _tmain(INT argc, char* argv[]) { 
    IC_CDR  CDR, *pThisCDRLeafData=NULL; 
    CDR_MAP  cdrMap; 
    CUST_MAP custMap; 
    KCI_MAP  kciMap; 
    
  4. 私は非常に幸運を持っていた、そしてそれはノードデータだとして重要なマップは、構造体を割り当てる有し、その構造体「アンカー」マップを持つ非常に満足していました。匿名の構造体はC++によって破棄されていますが(ひどい恐ろしい決定は必ず元に戻す必要があります)、最初の構造体メンバであるマップは匿名の構造体のように動作します。非常に滑らかでクリーンで、サイズ効果はゼロです。リーフ所有の構造体へのポインタ、または関数呼び出しの値による構造体のコピーを渡すと、どちらもうまく動作します。強くお勧めします。

  5. .insertの戻り値をトラップして、そのキー上に既存のノードが存在するかどうかを判断したり、新しいノードを作成したりすることができます。 (コードについては#12を参照してください)添字表記を使用してもこれは許されません。特に[]表記がマルチマップでは機能しないため、.insertとそれに固執する方が良いかもしれません。 ( "a"キーがないので、マルチマップで同じ値を持つ一連のキーがないので、そうするのは意味がありません)
  6. .eraseと。空白()(はい、これらの機能のいくつかは機能していて迷惑で、()のようなものが必要です。)
  7. マップノードのキー値とデータ値の両方を取得できます.firstと.secondを使用して、すべての地図を慣習的にキーとデータを返すために使用します。
  8. あなたは混乱と入力の莫大な量を節約し、マップ用のtypedefを使用します。

  9. イテレータのための "自動" 変数タイプを使用

    ULNG DestroyCustomer_callMap(CUST_SUM Summary, CDR_MAP& cdrMap, KCI_MAP& kciMap)と同様のtypedefと&演算子を使用してマップに

    typedef map<ULLNG, IC_CDR>  CDR_MAP;  
    typedef map<ULLNG, pIC_CDR>  CALL_MAP; 
    typedef struct { 
        CALL_MAP callMap; 
        ULNG  Knt;   
        DBL   BurnRateSec; 
        DBL   DeciCents; 
        ULLNG  tThen;  
        DBL   OldKCIKey; 
    } CUST_SUM, *pCUST_SUM; 
    typedef map<ULNG,CUST_SUM> CUST_MAP, CUST_MAP; 
    typedef map<DBL,pCUST_SUM> KCI_MAP; 
    
  10. パス参照。コンパイラは、残りのfor()ループ本体で指定された型から、使用するマップ型の型を調べます。それはとてもきれいなので、ほぼ魔法です!

    for(auto itr = Summary.callMap.begin(); itr!= Summary.callMap.end(); ++itr) {

  11. より意味のある).eraseと.empty(からのリターンを作るために、いくつかのマニフェスト定数を定義します。実際には参照カウントを維持している「スマートポインタ」は、あなたはいつも、おそらくクリーナー、そしてより多くの明白な方法で、独自の参照カウントを維持することができます覚えていることを考えると

    if(ERASE_SUCCESSFUL == cdrMap.erase (itr->second->iGUID)) {

  12. 。上記の#5と#10を組み合わせることで、このようなきれいなコードを書くことができます。 、自身のためにIEをすべて割り当てマップ・ノード上にハングアップするポインタを使用

    #define Pear(x,y) std::make_pair(x,y) // some macro magic 
    
    auto res = pSumStruct->callMap.insert(Pear(pCDR->iGUID,pCDR)); 
    if (! res.second) { 
        pCDR->RefKnt=2; 
    } else { 
        pCDR->RefKnt=1; 
        pSumStruct->Knt += 1; 
    } 
    
  13. :NOユーザポインタがオブジェクトをED)(ユーザのmallocを指していないが、潜在的に、より効率的であり、そしてするために使用され、うまく機能します私の経験で副作用を起こすことなくノードのデータを変更してください。

  14. 同じテーマでは、上記のpThisCDRLeafDataのように、そのようなポインタを非常に効果的に使用してノードの状態を保持することができます。これを特定のノードのデータを変更/変更する関数に渡すと、マップへの参照とノードpThisCDRLeafDataに戻るために必要なキーを渡すよりもきれいです。

  15. イテレータは魔法ではありません。値を取得するために地図をナビゲートしているので、高価で低速です。百万の値を保持するマップでは、キーに基づいてノードを1秒あたり約2,000万で読み取ることができます。イテレータでは、おそらく1000倍も遅いでしょう。

私は今のところそれについてカバーしていると思います。この変更がある場合、または共有する追加の洞察がある場合は更新されます。私は特にCコードでSTLを使うのを楽しんでいます。 IE:どこでも見えるクラスではありません。彼らは、私が取り組んでいる文脈では意味をなさない。それは問題ではない。がんばろう。

+1

malloc()とマップのmallocがあなたのコードを保持するために必要なので、マップがあなたのmalloc()からの戻り値を保持するためのポインタではなく、自分自身のデータを割り当てるトレードオフは、データは同じサイズになり、現在の冗長ポインタのメモリ要件が節約されます。 64ビットのポインタは、多くの記憶域を追加します。これは、保存されないものを格納するためのMap/Tree割り当て空間の内部ノードとトレードオフされます。ただし、ノードの追加または削除時にすべてのmalloc()が同じサイズになるため、ヒープの断片化は防げます。 – RocketRoy

+0

あなたが追加するかもしれないのは、new()の代わりにmalloc()を使用することです。新しい()へのmemcpy()の処理中にnewストレージ。これは、ストレージを初期化するのに必要な時間を問わず、完全かつ完全な無駄です。デバッグの目的でこれを行う場合は、calloc()を使用します。 STLはmalloc()とfree()を内部的に使用しているので、そのキューを取り出して実行します。 – RocketRoy

関連する問題