2009-03-13 3 views
2

私はプログラミングは初めてではありませんが、Javaで作業した後、私はC++に戻ってきて、ポインタではないクラス変数について少し混乱しています。次のコードを考える:C++はクラス変数の設定について質問します。

#include <iostream> 
#include <map> 

using namespace std; 

class Foo { 
    public: 
     Foo() { 
      bars[0]  = new Bar; 
      bars[0]->id = 5; 
     } 

     ~Foo() { } 

     struct Bar { 
      int id; 
     }; 

     void set_bars(map<int,Bar*>& b) { 
      bars = b; 
     } 

     void hello() { 
      cout << bars[0]->id << endl; 
     } 

    protected: 
     map<int,Bar*> bars; 
}; 

int main() { 
    Foo foo; 
    foo.hello(); 

    map<int,Foo::Bar*> testbars; 
    testbars[0]  = new Foo::Bar; 
    testbars[0]->id = 10; 

    foo.set_bars(testbars); 

    foo.hello(); 

    return(0); 
} 

私はしかし、C内の参照​​とポインタと、そのような程度の理解の私の不足が++、これは実際に野生で動作する場合、私は疑問にする場合、または5 & 10の予想される出力を得ますテストバーがスコープ外に出ると、バーフします。もちろん、ここでは、プログラムが終了する前にテストバーが範囲外に出ることはありませんが、別のクラス関数で関数変数として作成された場合はどうなりますか?とにかく、私の主な質問は、マップ・マップへのポインタとして棒グラフ変数を作成するほうが安全かと思いますか?

答えて

4

とにかく、私は私の主な質問は はそれがより良い/より安全な私はマップマップへのポインタ として バーのクラス変数を作成するための希望であると思いますか?

いいえ、C++はこれとはJavaのようなものではありません。あなたがポインタを使って自分に新しいオブジェクトをたくさん割り当てるのであれば、おそらく何か間違っているでしょう。物事を正しく行う方法を学ぶには、Accelerated C++のコピーを入手することをお勧めします。& Moo、

1

毎回、対応する削除が必要です。 deleteを呼び出した後にメモリを参照しようとすると、プログラムは実際には "barf"になります。

あなたがうまくいけば、それは簡単です。

メモリの所有権が明示的になるようにクラスを設計する必要があります。KNOWすべての割り当てで均等な割り当て解除が行われるようにする必要があります。 割り当てられたメモリを他のクラス/コンテナが削除するとは決して考えないでください。

これが役に立ちます。

0

以下のコードでは、Barsの地図を渡すことができ、クラス外のバーを変更することができます。

しかし、しかし、set_barsを再度呼び出さない限り。

1つのオブジェクトがBarsの作成と削除を担当している方が良いでしょう。あなたの場合はそうではありません。

場合によっては、Bars *の代わりにboost :: shared_ptr <バーを使用することもできます。それはJavaのような動作になります。

class Foo { 
public: 
    Foo() { 
     bars[0]  = new Bar; 
     bars[0]->id = 5; 
    } 

    ~Foo() { freeBarsMemory(); } 

    struct Bar { 
     int id; 
    }; 

    typedef std::map<int,Bar*> BarsList; 

    void set_bars(const BarsList& b) { 
     freeBarsMemory(); 
     bars = b; 
    } 

    void hello() { 
     std::cout << bars[0]->id << std::endl; 
    } 

protected: 
    BarsList bars; 

    void freeBarsMemory() 
    { 
     BarsList::const_iterator it = bars.begin(); 
     BarsList::const_iterator end = bars.end(); 

     for (; it != end; ++it) 
      delete it->second; 

     bars.clear(); 
    } 
}; 
+0

私は "bars.clear();"を追加します。 freeBarsMemory()の最後では、使用するのが少し安全です。 –

3

メンバ変数barsは、「辞書」様/連想配列のクラスの別のインスタンスです。 set_barsに代入すると、bの内容がbarsにコピーされます。したがって、相対ライフタイムがfootestbarsであることを心配する必要はありません。なぜなら、それらは独立した「価値のような」エンティティなのですから。

現在削除されることはないBarオブジェクトの寿命には、より多くの問題があります。それらを削除するためにコードを追加すると、(オブジェクト自体ではなく)Barオブジェクトのアドレスをコピーしているため、別の2つのマップが同じオブジェクトを指しているため、さらに問題が発生します。オブジェクトが削除されると、もう一方のマップは引き続きそのオブジェクトを参照します。これはC++の疫病のように避けなければならない種類のものです! newで割り当てられたオブジェクトへの裸のポインタは、起こるのを待っている災害です。

参考文献(&で宣言されています)は、オブジェクトの存続期間に関するポインタと違いはありません。同じオブジェクトを2つの場所から参照できるようにするには、ポインタまたは参照のどちらかを使用できますが、依然として割り当て解除の問題が残ります。

最新のC++環境(std::tr1)に含まれるshared_ptrのようなクラスを使用すると、割り当て解除の問題を解決する方法を得ることができます。しかし、あなたは周期的なポインタネットワークで問題を起こすかもしれません(AとBのポイントはAなど)。これは自動的にはクリーンアップされません。

+0

Barオブジェクト内のデータを操作するために使用する別のクラスがあります。そのクラスがBar変数の値を変更するとき、Barオブジェクトを戻す必要がないように、変更を維持する必要があります。代わりに私のマップで参照(バー&)を使用する必要がありますか? –

+0

私の答えにもう少し追加しました。 NB。私は1994年以来C++を商業的に使ってきました。そして、Java/C#プログラマーはこれを今学びたいと思っています。今日のほとんどの職場では、それは正しいツールではありません。単純なことさえ正しく実行するのは不必要に苦痛です。 –

0

私はプログラミングに慣れていませんが、Javaで作業した後、私はC++に戻り、ポインタではないクラス変数について少し混乱しています。

混乱は、ヒープ上のデータと必ずしもヒープ上にないデータの組み合わせから生じるように見えます。これは混乱の一般的な原因です。

投稿したコードでは、barsはポインタではありません。それはクラススコープのため、オブジェクトを含むオブジェクト(testbars)が破棄されるまで存在します。この場合、testbarsがスタック上に作成されたため、スコープがどれくらい深くネストされているかにかかわらず、スコープから外れると破棄されます。 testbarsが破壊された場合、testbarsのサブオブジェクト(それらが親クラスであるかtestbarsオブジェクトに含まれるオブジェクトであるか)は、その瞬間に明確な順序でデストラクタを実行します。

これはC++の非常に強力な側面です。ネットワーク接続を開き、ヒープ上にメモリを割り当て、データをファイルに書き込む10行のコンストラクタを持つクラスを想像してみましょう。クラスのデストラクタがそれをすべて元に戻したとします(ネットワーク接続を閉じる、ヒープ上のメモリを解放する、ファイルを閉じるなど)。今、このクラスのオブジェクトの作成がコンストラクタの途中で失敗したとします(たとえば、ネットワーク接続がダウンしている)。どのようにして、デストラクタのどの行が成功したコンストラクタの部分を元に戻すかをプログラムが知ることができますか?これを知る一般的な方法はないので、そのオブジェクトのデストラクタは実行されません。

ここで、10個のオブジェクトを含むクラスを想像してください。これらのオブジェクトのコンストラクタは、ロールバックする必要があります(ネットワーク接続を開く、ヒープ上にメモリを割り当てる、データをファイルに書き込むなど)これらのオブジェクトのそれぞれのデストラクタには、アクションをロールバックするために必要なコードが含まれています(ネットワーク接続を閉じる、オブジェクトの割り当てを解除する、ファイルを閉じるなど)。 5つのオブジェクトだけが正常に作成された場合、それらの5つだけを破壊する必要があり、そのデストラクタはその時点で正確に実行されます

testbarsがヒープ上に作成された場合(new経由)、deleteを呼び出すと破棄されます。一般に、オブジェクトが作成されたスコープよりも長持ちする理由がない限り、スタック上のオブジェクトを使用する方がはるかに簡単です。

これはFoo::barになります。 Foo::barsは、ヒープ上のオブジェクトを参照するmapです。つまり、このコード例では、ヒープに割り当てられたオブジェクトを参照するポインタを指します(ポインタはスタックに割り当てられたオブジェクトを参照することもできます)。これらのポインタが参照するオブジェクトを投稿した例では、決してdelete dであり、これらのオブジェクトはヒープ上にあるため、小さなメモリリーク(オペレーティングシステムがプログラム終了時にクリーンアップする)が発生しています。 STLによると、Foo::barのようにdeleteポインタのように彼らが破壊されて参照してください。 Boostにはこの問題の解決策がいくつかあります。あなたの場合は、ヒープ上にこれらのオブジェクトを単に割り当てない方が簡単でしょう:

#include <iostream> 
#include <map> 

using std::map; 
using std::cout; 

class Foo { 
    public: 
     Foo() { 
      // normally you wouldn't use the parenthesis on the next line 
      // but we're creating an object without a name, so we need them 
      bars[0] = Bar(); 
      bars[0].id = 5; 
     } 

     ~Foo() { } 

     struct Bar { 
      int id; 
     }; 

     void set_bars(map<int,Bar>& b) { 
      bars = b; 
     } 

     void hello() { 
      cout << bars[0].id << endl; 
     } 

    protected: 
     map<int,Bar> bars; 
}; 

int main() { 
    Foo foo; 
    foo.hello(); 

    map<int,Foo::Bar> testbars; 
    // create another nameless object 
    testbars[0] = Foo::Bar(); 
    testbars[0].id = 10; 

    foo.set_bars(testbars); 
    foo.hello(); 
    return 0; 
} 
関連する問題