回答:
何が起こっている
書き込むT t;
ときはT
、自動ストレージ期間を持つタイプのオブジェクトを作成しています。範囲外になると、自動的にクリーンアップされます。
書き込むnew T()
ときはT
、動的ストレージ期間を持つタイプのオブジェクトを作成しています。自動的にクリーンアップされません。
delete
クリーンアップするために、それへのポインタを渡す必要があります。
ただし、2番目の例の方が悪いです。ポインタを逆参照し、オブジェクトのコピーを作成しています。これによりnew
、で作成されたオブジェクトへのポインタが失われるため、必要な場合でも削除することはできません。
あなたがすべきこと
自動保存期間を優先する必要があります。新しいオブジェクトが必要です、ただ書いてください:
A a; // a new object of type A
B b; // a new object of type B
動的ストレージ期間が必要な場合は、割り当てられたオブジェクトへのポインタを、自動削除する自動ストレージ期間オブジェクトに保存します。
template <typename T>
class automatic_pointer {
public:
automatic_pointer(T* pointer) : pointer(pointer) {}
// destructor: gets called upon cleanup
// in this case, we want to use delete
~automatic_pointer() { delete pointer; }
// emulate pointers!
// with this we can write *p
T& operator*() const { return *pointer; }
// and with this we can write p->f()
T* operator->() const { return pointer; }
private:
T* pointer;
// for this example, I'll just forbid copies
// a smarter class could deal with this some other way
automatic_pointer(automatic_pointer const&);
automatic_pointer& operator=(automatic_pointer const&);
};
automatic_pointer<A> a(new A()); // acts like a pointer, but deletes automatically
automatic_pointer<B> b(new B()); // acts like a pointer, but deletes automatically
これは、あまり説明的ではない名前RAII(Resource Acquisition Is Initialization)に従う一般的なイディオムです。クリーンアップが必要なリソースを取得すると、自動ストレージ期間のオブジェクトに貼り付けられるため、クリーンアップを心配する必要がありません。これは、メモリ、開いているファイル、ネットワーク接続など、あらゆるリソースに当てはまります。
このautomatic_pointer
ことはすでにさまざまな形で存在していますが、例を示すために提供しました。と呼ばれる非常に類似したクラスが標準ライブラリに存在しstd::unique_ptr
ます。
古い(C ++ 11より前の)名前の付いauto_ptr
たものもありますが、奇妙なコピー動作があるため、非推奨になりました。
そしてstd::shared_ptr
、同じオブジェクトへの複数のポインタを許可し、最後のポインタが破棄されたときにのみそれをクリーンアップするような、よりスマートな例がいくつかあります。
*p += 2
、通常のポインタの場合と同じようにを実行できます。参照によって返されない場合は、通常のポインターの動作を模倣しません。これは、ここでの意図です。
ステップバイステップの説明:
// creates a new object on the heap:
new B()
// dereferences the object
*(new B())
// calls the copy constructor of B on the object
B object2 = *(new B());
つまり、この時点で、ヒープ上にポインターがないオブジェクトがあり、削除することは不可能です。
他のサンプル:
A *object1 = new A();
delete
割り当てられたメモリを忘れた場合のみのメモリリークです。
delete object1;
C ++には、自動ストレージを備えたオブジェクト、スタック上に作成されて自動的に破棄されるオブジェクト、動的ストレージを備えたオブジェクトがヒープ上にあり、それらを使用して割り当てをnew
行い、で解放する必要がありますdelete
。(これはすべて大雑把に言えば)
でdelete
割り当てられたすべてのオブジェクトにがあるはずだと思いますnew
。
編集
考えてobject2
みれば、メモリリークである必要はありません。
次のコードは、ポイントを作成するためのものです。これは悪い考えです。次のようなコードは好きではありません。
class B
{
public:
B() {}; //default constructor
B(const B& other) //copy constructor, this will be called
//on the line B object2 = *(new B())
{
delete &other;
}
}
この場合、other
は参照によって渡されるため、が指すオブジェクトとまったく同じになりますnew B()
。したがって、アドレスを取得し&other
てポインタを削除すると、メモリが解放されます。
しかし、私はこれを十分に強調することはできません、これをしないでください。要点はここにあります。
2つの「オブジェクト」があるとします。
obj a;
obj b;
それらはメモリ内の同じ場所を占有しません。言い換えると、&a != &b
一方の値をもう一方に割り当てても、場所は変更されませんが、内容が変更されます。
obj a;
obj b = a;
//a == b, but &a != &b
直感的には、ポインタ「オブジェクト」は同じように機能します。
obj *a;
obj *b = a;
//a == b, but &a != &b
次に、例を見てみましょう。
A *object1 = new A();
これはの値をnew A()
に割り当てていますobject1
。値はポインタ、意味のあるobject1 == new A()
、しかし&object1 != &(new A())
。(この例は有効なコードではなく、説明のみを目的としています)
ポインターの値は保持されるため、ポインターが指すメモリーを解放できdelete object1;
ます。ルールにより、これはdelete (new A());
リークがない場合と同じように動作します。
2番目の例では、ポイントされたオブジェクトをコピーしています。値はそのオブジェクトの内容であり、実際のポインターではありません。他のすべての場合と同様に、&object2 != &*(new A())
。
B object2 = *(new B());
割り当てられたメモリへのポインタを失ったため、解放できません。delete &object2;
それはうまくいくように見えるかもしれ&object2 != &*(new A())
ませんが、それはと同等ではないdelete (new A())
ので無効です。
C#およびJavaでは、newを使用して任意のクラスのインスタンスを作成し、後でそれを破棄することを心配する必要はありません。
C ++には、オブジェクトを作成するキーワード「new」もありますが、JavaやC#とは異なり、オブジェクトを作成する唯一の方法ではありません。
C ++には、オブジェクトを作成するための2つのメカニズムがあります。
自動作成では、スコープ環境でオブジェクトを作成します。-関数内または-クラス(または構造体)のメンバーとして。
関数では、次のように作成します。
int func()
{
A a;
B b( 1, 2 );
}
クラス内では通常、次のように作成します。
class A
{
B b;
public:
A();
};
A::A() :
b( 1, 2 )
{
}
最初のケースでは、スコープブロックが終了すると、オブジェクトが自動的に破棄されます。これは、関数または関数内のスコープブロックである可能性があります。
後者の場合、オブジェクトbは、それがメンバーであるAのインスタンスとともに破棄されます。
オブジェクトの存続期間を制御する必要があり、オブジェクトを破棄するには削除が必要な場合、オブジェクトはnewで割り当てられます。RAIIと呼ばれる手法では、自動オブジェクト内に配置することで、オブジェクトの作成時にオブジェクトを削除し、その自動オブジェクトのデストラクタが有効になるのを待ちます。
そのようなオブジェクトの1つは、「deleter」ロジックを呼び出すshared_ptrですが、オブジェクトを共有しているshared_ptrのすべてのインスタンスが破棄された場合のみです。
一般に、コードにはnewへの多くの呼び出しがある可能性がありますが、deleteへの呼び出しは制限する必要があり、これらがスマートポインターに入れられるデストラクターまたは「deleter」オブジェクトから呼び出されることを常に確認する必要があります。
デストラクタも例外をスローしないでください。
これを行うと、メモリリークがほとんどなくなります。
automatic
および以外にもありdynamic
ます。もありstatic
ます。
B object2 = *(new B());
このラインがリークの原因です。これを少し分けてみましょう。
object2はタイプBの変数で、たとえばアドレス1に格納されています(はい、ここでは任意の数を選択しています)。右側で、新しいB、またはタイプBのオブジェクトへのポインターを要求しました。プログラムはこれを喜んで与え、新しいBをアドレス2に割り当て、アドレス3にポインターを作成します。アドレス2のデータにアクセスする唯一の方法は、アドレス3のポインターを経由することです。次に、を使用*
してポインターを逆参照し、ポインターが指しているデータ(アドレス2のデータ)を取得しました。これにより、そのデータのコピーが効果的に作成され、アドレス1に割り当てられたobject2に割り当てられます。これは、オリジナルではなくコピーであることを忘れないでください。
さて、ここに問題があります:
実際にそのポインターを使用可能な場所に保存したことはありません。この割り当てが完了すると、ポインタ(address2へのアクセスに使用したaddress3のメモリ)はスコープ外になり、到達できなくなります。削除を呼び出すことができなくなったため、address2のメモリをクリーンアップできません。残っているのは、address1のaddress2からのデータのコピーです。記憶にある同じものの2つ。1つはアクセスでき、もう1つはアクセスできません(パスを失ったため)。これがメモリリークの原因です。
C ++のポインタがどのように機能するかについてよく読んで、C#の背景から学ぶことをお勧めします。これらは高度なトピックであり、理解するのに時間がかかる場合がありますが、それらの使用はあなたにとって非常に貴重です。
それがより簡単になれば、コンピューターのメモリはホテルのようなものであり、プログラムは必要なときに部屋を借りる顧客です。
このホテルの仕組みは、部屋を予約し、出発するときにポーターに伝えることです。
プログラムして部屋を予約し、ポーターに知らせずに退出すると、ポーターは部屋がまだ使用中であると見なし、他の誰にもそれを使用させません。この場合、部屋に漏れがあります。
プログラムがメモリを割り当て、メモリを削除しない場合(使用を停止するだけ)、コンピュータはメモリがまだ使用中であると見なし、他の人がメモリを使用することを許可しません。これはメモリリークです。
これは正確なアナロジーではありませんが、役立つかもしれません。
作成object2
すると、newで作成したオブジェクトのコピーが作成されますが、(割り当てられていない)ポインターも失われます(そのため、後で削除する方法はありません)。これを回避するにはobject2
、参照を作成する必要があります。
まあ、メモリnew
ポインターをオペレーターに渡して、オペレーターを使用して割り当てたメモリを解放しないと、メモリリークが発生しますdelete
。
上記の2つの場合:
A *object1 = new A();
ここでdelete
はメモリを解放するために使用していないため、object1
ポインタがスコープから外れると、メモリリークが発生します。ポインタを失い、そのdelete
上で演算子を使用できないためです。
そしてここ
B object2 = *(new B());
によって返されたポインタを破棄しnew B()
ているdelete
ため、メモリを解放するためにそのポインタを渡すことはできません。したがって、別のメモリリーク。
すぐにリークしているのは次の行です。
B object2 = *(new B());
ここではB
、ヒープに新しいオブジェクトを作成し、スタックにコピーを作成しています。ヒープに割り当てられたものにはアクセスできなくなり、リークが発生します。
この行はすぐには漏れません:
A *object1 = new A();
あなたが決してdelete
dをしなければリークがありobject1
ます。