web-dev-qa-db-ja.com

C ++で動的に割り当てられたオブジェクトへのポインタのベクトルを使用するときにメモリリークを回避するにはどうすればよいですか?

オブジェクトへのポインターのベクトルを使用しています。これらのオブジェクトは基本クラスから派生し、動的に割り当てられて保存されています。

たとえば、次のようなものがあります。

vector<Enemy*> Enemies;

そして、私はEnemyクラスから派生し、次のように派生クラスにメモリを動的に割り当てます。

enemies.Push_back(new Monster());

メモリリークやその他の問題を回避するために注意する必要があることは何ですか?

66
akif

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_ptrsがいくつ存在するかを追跡し、最後のオブジェクトが存在しなくなる(カウントがゼロになる)と、ポインターを解放します。コピーすると、参照カウントが増加するだけです(そして、所有権を移動すると、より低い、ほぼ無料のコストで所有権を移動します)。 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_ptrstd::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>>を使用します。とにかく共有することを期待します。プロファイリングがそうでないと言うまでは十分に高速で、安全で、使いやすいです。

145
GManNickG

私は次のことを仮定しています:

  1. Vector <base *>のようなベクターがあります
  2. ヒープ上のオブジェクトを割り当てた後、このベクトルへのポインターをプッシュしています
  3. このベクターへの派生*ポインターのPush_backを実行します。

次のことが頭に浮かぶ:

  1. Vectorは、ポインターが指すオブジェクトのメモリーを解放しません。それ自体を削除する必要があります。
  2. ベクトル固有のものはありませんが、基本クラスのデストラクターは仮想でなければなりません。
  3. vector <base *>およびvector <derived *>は、まったく異なる2つのタイプです。
9
Naveen

vector<T*>を使用する際の問題は、ベクトルが予期せずスコープから外れた場合(例外がスローされた場合など)に、ベクトルは自分の後にクリーンアップしますが、これはpointer、ポインタが参照しているものに割り当てたメモリではありません。 GManのdelete_pointed_to関数 は、何も問題がない場合にのみ機能するため、制限された値です。

あなたがする必要があるのは、スマートポインタを使用することです:

vector< std::tr1::shared_ptr<Enemy> > Enemies;

(std libにTR1が付属していない場合は、代わりにboost::shared_ptrを使用してください。)非常にまれなコーナーケース(循環参照)を除き、これによりオブジェクトの有効期間の問題が解消されます。

Edit:GManは、彼の詳細な回答で、これにも言及していることに注意してください。

9
sbi