オブジェクトへのポインターのベクトルを使用しています。これらのオブジェクトは基本クラスから派生し、動的に割り当てられて保存されています。
たとえば、次のようなものがあります。
vector<Enemy*> Enemies;
そして、私はEnemyクラスから派生し、次のように派生クラスにメモリを動的に割り当てます。
enemies.Push_back(new Monster());
メモリリークやその他の問題を回避するために注意する必要があることは何ですか?
std::vector
は、いつものようにメモリを管理しますが、このメモリはオブジェクトではなくポインタです。
これは、ベクトルがスコープから外れると、クラスがメモリ内で失われることを意味します。例えば:
#include <vector>
struct base
{
virtual ~base() {}
};
struct derived : base {};
typedef std::vector<base*> container;
void foo()
{
container c;
for (unsigned i = 0; i < 100; ++i)
c.Push_back(new derived());
} // leaks here! frees the pointers, doesn't delete them (nor should it)
int main()
{
foo();
}
あなたがする必要があるのは、ベクトルが範囲外になる前にすべてのオブジェクトを削除することを確認することです:
#include <algorithm>
#include <vector>
struct base
{
virtual ~base() {}
};
struct derived : base {};
typedef std::vector<base*> container;
template <typename T>
void delete_pointed_to(T* const ptr)
{
delete ptr;
}
void foo()
{
container c;
for (unsigned i = 0; i < 100; ++i)
c.Push_back(new derived());
// free memory
std::for_each(c.begin(), c.end(), delete_pointed_to<base>);
}
int main()
{
foo();
}
ただし、何らかのアクションを実行することを覚えておく必要があるため、これを維持するのは困難です。さらに重要なことは、要素の割り当てと割り当て解除ループの間に例外が発生した場合、割り当て解除ループは実行されず、とにかくメモリリークが発生することです。これは例外安全性と呼ばれ、割り当て解除を自動的に行う必要がある重要な理由です。
ポインターが自分自身を削除した場合が良いでしょう。これらはスマートポインターと呼ばれ、標準ライブラリは std::unique_ptr
および std::shared_ptr
を提供します。
std::unique_ptr
は、リソースへの一意の(非共有、単一所有者)ポインターを表します。これは、デフォルトのスマートポインターであり、生のポインターの使用全体を完全に置き換える必要があります。
auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself
std::make_unique
はC++ 11標準に見落としがありますが、自分で作成できます。 unique_ptr
(できればmake_unique
よりもお勧めできません)を直接作成するには、次のようにします。
std::unique_ptr<derived> myresource(new derived());
一意のポインターには移動のセマンティクスのみがあります。コピーできません:
auto x = myresource; // error, cannot copy
auto y = std::move(myresource); // okay, now myresource is empty
そして、コンテナで使用するために必要なのはこれだけです。
#include <memory>
#include <vector>
struct base
{
virtual ~base() {}
};
struct derived : base {};
typedef std::vector<std::unique_ptr<base>> container;
void foo()
{
container c;
for (unsigned i = 0; i < 100; ++i)
c.Push_back(make_unique<derived>());
} // all automatically freed here
int main()
{
foo();
}
shared_ptr
には、参照カウントのコピーセマンティクスがあります。複数の所有者がオブジェクトを共有できます。オブジェクトにshared_ptr
sがいくつ存在するかを追跡し、最後のオブジェクトが存在しなくなる(カウントがゼロになる)と、ポインターを解放します。コピーすると、参照カウントが増加するだけです(そして、所有権を移動すると、より低い、ほぼ無料のコストで所有権を移動します)。 std::make_shared
で作成します(または上記のように直接、ただしshared_ptr
は内部的に割り当てを行う必要があるため、make_shared
を使用する方が一般的に効率的で技術的に例外安全です)。
#include <memory>
#include <vector>
struct base
{
virtual ~base() {}
};
struct derived : base {};
typedef std::vector<std::shared_ptr<base>> container;
void foo()
{
container c;
for (unsigned i = 0; i < 100; ++i)
c.Push_back(std::make_shared<derived>());
} // all automatically freed here
int main()
{
foo();
}
通常、std::unique_ptr
はより軽量なのでデフォルトとして使用することを忘れないでください。さらに、std::shared_ptr
はstd::unique_ptr
から構築することができます(ただし、その逆はできません)ので、小さく始めても構いません。
または、 boost::ptr_container
などのオブジェクトへのポインタを格納するために作成されたコンテナを使用できます。
#include <boost/ptr_container/ptr_vector.hpp>
struct base
{
virtual ~base() {}
};
struct derived : base {};
// hold pointers, specially
typedef boost::ptr_vector<base> container;
void foo()
{
container c;
for (int i = 0; i < 100; ++i)
c.Push_back(new Derived());
} // all automatically freed here
int main()
{
foo();
}
boost::ptr_vector<T>
はC++ 03で明らかに使用されていましたが、std::vector<std::unique_ptr<T>>
をほぼ同等のオーバーヘッドなしで使用できるため、関連性について話すことはできませんが、この主張はテストする必要があります。
とにかく、コード内の物を明示的に解放しないでください。物事をまとめて、リソース管理が自動的に処理されるようにします。あなたのコードに生の所有ポインタがあってはなりません。
ゲームのデフォルトとして、おそらくstd::vector<std::shared_ptr<T>>
を使用します。とにかく共有することを期待します。プロファイリングがそうでないと言うまでは十分に高速で、安全で、使いやすいです。
私は次のことを仮定しています:
次のことが頭に浮かぶ:
vector<T*>
を使用する際の問題は、ベクトルが予期せずスコープから外れた場合(例外がスローされた場合など)に、ベクトルは自分の後にクリーンアップしますが、これはpointer、ポインタが参照しているものに割り当てたメモリではありません。 GManのdelete_pointed_to
関数 は、何も問題がない場合にのみ機能するため、制限された値です。
あなたがする必要があるのは、スマートポインタを使用することです:
vector< std::tr1::shared_ptr<Enemy> > Enemies;
(std libにTR1が付属していない場合は、代わりにboost::shared_ptr
を使用してください。)非常にまれなコーナーケース(循環参照)を除き、これによりオブジェクトの有効期間の問題が解消されます。
Edit:GManは、彼の詳細な回答で、これにも言及していることに注意してください。