次のコードをコーディングします。
class Foo
{
Monster* monsters[6];
Foo()
{
for (int i = 0; i < 6; i++)
{
monsters[i] = new Monster();
}
}
virtual ~Foo();
}
正しいデストラクタとは何ですか?
この:
Foo::~Foo()
{
delete [] monsters;
}
またはこれ:
Foo::~Foo()
{
for (int i = 0; i < 6; i++)
{
delete monsters[i];
}
}
私は現在、最上位のコンストラクタを持ち、すべてが順調に機能していますが、もちろん、それが漏れているかどうかはわかりません...
個人的には、私がやっていることを考えると、2番目のバージョンははるかに論理的だと思います。とにかく、これを行うための「適切な」方法は何ですか?
delete[] monsters;
monsters
は動的に割り当てられた配列へのポインターではないため、不正です。それはisポインターの配列です。クラスメンバとして、クラスインスタンスが破棄されると自動的に破棄されます。
配列内のポインターが動的に割り当てられたMonster
オブジェクトを指すので、他の実装は正しい実装です。
現在のメモリ割り当て戦略では、意図しないコピーによって二重削除が発生しないように、おそらく独自のコピーコンストラクターとコピー割り当て演算子を宣言することに注意してください。 (コピーを防止したい場合は、プライベートとして宣言し、実際に実装しないようにすることができます。)
new
には、delete
を使用する必要があります。ために new[]
つかいます delete[]
。 2番目のバリアントは正しいです。
2番目のものは、状況下で正しいです(とにかく、最も間違っている、とにかく)。
編集:元のコードのように、最初の場所でnew
またはdelete
を使用する正当な理由がないことを示しているため、「最も間違っています」。
std::vector<Monster> monsters;
その結果、コードがよりシンプルになり、責任がより明確に分離されます。
アンスウェアを簡素化するために、次のコードを見てみましょう。
#include "stdafx.h"
#include <iostream>
using namespace std;
class A
{
private:
int m_id;
static int count;
public:
A() {count++; m_id = count;}
A(int id) { m_id = id; }
~A() {cout<< "Destructor A " <<m_id<<endl; }
};
int A::count = 0;
void f1()
{
A* arr = new A[10];
//delete operate only one constructor, and crash!
delete arr;
//delete[] arr;
}
int main()
{
f1();
system("PAUSE");
return 0;
}
出力は、デストラクタA 1であり、クラッシュします(式:_BLOCK_TYPE_IS_VALID(phead-nBlockUse))。
使用する必要があります:delete [] arr; 1つのセルだけでなく、配列全体を削除するためです!
delete [] arrを使用してみてください。出力は次のとおりです。デストラクタA 10デストラクタA 9デストラクタA 8デストラクタA 7デストラクタA 6デストラクタA 5デストラクタA 4デストラクタA 3デストラクタA 2デストラクタA 1
同じ原則は、ポインターの配列にも当てはまります。
void f2()
{
A** arr = new A*[10];
for(int i = 0; i < 10; i++)
{
arr[i] = new A(i);
}
for(int i = 0; i < 10; i++)
{
delete arr[i];//delete the A object allocations.
}
delete[] arr;//delete the array of pointers
}
delete [] arrの代わりにdelete arrを使用する場合。配列内のポインター全体は削除されません=>ポインターオブジェクトのメモリリーク!
delete[] monsters
は間違いです。ヒープデバッガーには次の出力が表示されます。
allocated non-array memory at 0x3e38f0 (20 bytes)
allocated non-array memory at 0x3e3920 (20 bytes)
allocated non-array memory at 0x3e3950 (20 bytes)
allocated non-array memory at 0x3e3980 (20 bytes)
allocated non-array memory at 0x3e39b0 (20 bytes)
allocated non-array memory at 0x3e39e0 (20 bytes)
releasing array memory at 0x22ff38
ご覧のとおり、間違った形式の削除(非配列と配列)でリリースしようとしていますが、ポインター0x22ff38がnewの呼び出しによって返されたことはありません。 2番目のバージョンは正しい出力を示しています。
[allocations omitted for brevity]
releasing non-array memory at 0x3e38f0
releasing non-array memory at 0x3e3920
releasing non-array memory at 0x3e3950
releasing non-array memory at 0x3e3980
releasing non-array memory at 0x3e39b0
releasing non-array memory at 0x3e39e0
とにかく、最初はデストラクタを手動で実装する必要のない設計を好みます。
#include <array>
#include <memory>
class Foo
{
std::array<std::shared_ptr<Monster>, 6> monsters;
Foo()
{
for (int i = 0; i < 6; ++i)
{
monsters[i].reset(new Monster());
}
}
virtual ~Foo()
{
// nothing to do manually
}
};
2番目の例は正しいです。 monsters
配列自体を削除する必要はなく、作成した個々のオブジェクトだけを削除する必要があります。
コードが次のようであれば、それは理にかなっています。
#include <iostream>
using namespace std;
class Monster
{
public:
Monster() { cout << "Monster!" << endl; }
virtual ~Monster() { cout << "Monster Died" << endl; }
};
int main(int argc, const char* argv[])
{
Monster *mon = new Monster[6];
delete [] mon;
return 0;
}
各ポインターを個別に削除してから、配列全体を削除します。配列に格納されるクラスに対して適切なデストラクタを定義したことを確認してください。そうしないと、オブジェクトが適切にクリーンアップされたことを確認できません。すべてのデストラクタが仮想であることを確認して、継承とともに使用したときに適切に動作するようにしてください。