オブジェクトへのポインタのベクトルを使用しています。これらのオブジェクトは基本クラスから派生し、動的に割り当てられて保存されます。
たとえば、私は次のようなものを持っています:
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
、最後のオブジェクトが存在しなくなると(そのカウントはゼロになります)、ポインターを解放します。コピーすると参照数が増えるだけです(そして移動すると所有権がより低く、ほぼ無料で譲渡されます)。それらを使用して作成します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*>
は、ベクターが予期せずスコープから外れると(例外がスローされた場合など)、ベクターが自分の後でクリーンアップすることですが、これにより、ポインターを保持するために管理するメモリのみが解放され、割り当てたメモリは解放されません。ポインタが参照しているものについて。したがって、GManのdelete_pointed_to
機能は、何も問題がない場合にのみ機能するため、価値が限られています。
あなたがする必要があるのは、スマートポインタを使用することです:
vector< std::tr1::shared_ptr<Enemy> > Enemies;
(std libにTR1がない場合は、boost::shared_ptr
代わりに使用してください。)非常にまれなコーナーケース(循環参照)を除いて、これは単にオブジェクトの存続期間の問題を取り除きます。
編集:GManは、彼の詳細な回答の中で、これについても言及していることに注意してください。
私は次のことを想定しています:
次のことが頭に浮かびます。
非常に注意すべきことの1つは、内容が同じ値のMonster()DERIVEDオブジェクトが2つある場合です。ベクトルからDUPLICATEMonsterオブジェクト(DERIVED MonsterオブジェクトへのBASEクラスポインタ)を削除したいとします。重複を削除するために標準のイディオムを使用した場合(並べ替え、一意、消去:リンク#2を参照)、メモリリークの問題や重複削除の問題が発生し、セグメンテーションの無効化につながる可能性があります(私はこれらの問題を個人的に見ましたLINUXマシン)。
std :: unique()の問題は、ベクトルの最後にある[duplicatePosition、end)範囲[包括的、排他的)の重複が?として定義されていないことです。発生する可能性があるのは、これらの未定義((?)アイテムが余分に重複しているか、重複が欠落している可能性があることです。
問題は、std :: unique()がポインタのベクトルを適切に処理するように調整されていないことです。その理由は、std :: uniqueが、ベクトルの「下」の終わりからベクトルの最初に向かって一意をコピーするためです。プレーンオブジェクトのベクトルの場合、これによりCOPY CTORが呼び出され、COPY CTORが適切に書き込まれていれば、メモリリークの問題は発生しません。ただし、ポインタのベクトルの場合、「ビット単位のコピー」以外にCOPY CTORがないため、ポインタ自体が単純にコピーされます。
これらは、スマートポインタを使用する以外に、これらのメモリリークを解決する方法です。std :: unique()の独自のわずかに変更されたバージョンを「your_company :: unique()」として記述する1つの方法。基本的なトリックは、要素をコピーする代わりに、2つの要素を交換することです。また、2つのポインターを比較する代わりに、オブジェクト自体への2つのポインターに続くBinaryPredicateを呼び出し、それら2つの「Monster」派生オブジェクトの内容を比較することを確認する必要があります。
1)@SEE_ALSO: http ://www.cplusplus.com/reference/algorithm/unique/
2)@SEE_ALSO: 重複を消去してベクトルを並べ替える最も効率的な方法は何ですか?
2番目のリンクは適切に記述されており、std :: vectorで機能しますが、メモリリークがあり、std :: vectorの解放が重複しています(SEGMENTATION違反が発生する場合があります)。
3)@SEE_ALSO:valgrind(1)。LINUXのこの「メモリリーク」ツールは、それが見つけることができるもので素晴らしいです!私はそれを使用することを強くお勧めします!
今後の投稿で「my_company::unique()」の素敵なバージョンを投稿したいと思います。現時点では、完全ではありません。BinaryPredicateを含む3引数バージョンが、関数ポインターまたはFUNCTORのいずれかでシームレスに機能するようにしたいのですが、両方を適切に処理するのに問題があります。これらの問題を解決できない場合は、私が持っているものを投稿し、コミュニティに私がこれまでに行ったことを改善してもらうようにします。