簡體   English   中英

在C ++中使用指向動態分配對象的指針向量時,如何避免內存泄漏?

[英]How to avoid memory leaks when using a vector of pointers to dynamically allocated objects in C++?

我正在使用一個指向對象的向量。 這些對象派生自基類,並且正在動態分配和存儲。

例如,我有類似的東西:

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_ptrstd::shared_ptr

std::unique_ptr表示指向某個資源的唯一(非共享,單個所有者)指針。 這應該是您的默認智能指針,並且整體完全替換任何原始指針使用。

auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself

通過監督,C ++ 11標准中缺少std::make_unique ,但您可以自己制作一個。 要直接創建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>> 無論如何,我們希望共享,它足夠快,直到分析說不然,它是安全的,並且它易於使用。

我假設如下:

  1. 你有一個像vector <base *>這樣的矢量
  2. 在堆上分配對象后,您正在將指針推送到此向量
  3. 你想在這個向量中執行派生*指針的push_back。

以下事情浮現在我的腦海中:

  1. Vector不會釋放指針指向的對象的內存。 你必須自己刪除它。
  2. 沒有什么特定於vector,但基類析構函數應該是虛擬的。
  3. vector <base *>和vector <derived *>是兩種完全不同的類型。

使用vector<T*>的麻煩在於,每當向量意外地超出范圍時(比如拋出異常時),向量會自行清理,但這只會釋放它為保持指針而管理的內存,不是你為指針所指的內存分配的內存。 所以GMan的delete_pointed_to函數價值有限,因為它只在沒有出錯的情況下才有效。

你需要做的是使用智能指針:

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

(如果你的std lib沒有TR1,請使用boost::shared_ptr 。)除了非常罕見的極端情況(循環引用)之外,這簡單地消除了對象生存期的麻煩。

編輯 :請注意,GMan在他的詳細答案中也提到了這一點。

有一件事要非常小心,如果有兩個Monster()DERIVED對象,其內容值相同。 假設您要從向量中刪除DUPLICATE Monster對象(BASE類指向DERIVED Monster對象)。 如果您使用標准慣用法來刪除重復項(排序,唯一,擦除:請參閱鏈接#2),您將遇到內存泄漏問題和/或重復刪除問題,可能導致SEGMENTATION VOIOLATIONS(我個人看到了這些問題) LINUX機器)。

std :: unique()的問題是,向量末尾的[duplicatePosition,end]范圍[包含,獨占]中的重復項未定義為?。 可能發生的是那些未定義的((?)項可能是額外的重復或缺少重復。

問題是std :: unique()不能正確處理指針向量。 原因是std :: unique副本從向量的末尾“向下”朝向向量的開頭是唯一的。 對於普通對象的向量,這將調用COPY CTOR,如果正確寫入COPY CTOR,則不存在內存泄漏問題。 但是當它是一個指針向量時,除了“按位復制”之外沒有COPY CTOR,所以指針本身只是被復制了。

除了使用智能指針之外,還有解決這些內存泄漏的方法。 將自己的略微修改版本的std :: unique()編寫為“your_company :: unique()”的一種方法。 基本的技巧是,不要復制元素,而是交換兩個元素。 而且你必須確保不是比較兩個指針,而是調用BinaryPredicate,它跟隨對象本身的兩個指針,並比較這兩個“Monster”派生對象的內容。

1)@SEE_ALSO: http ://www.cplusplus.com/reference/algorithm/unique/

2)@SEE_ALSO: 刪除重復項和排序向量的最有效方法是什么?

第二個鏈接寫得非常好,並且可以用於std :: vector但是有std :: vector的內存泄漏,重復釋放(有時會導致SEGMENTATION違規)

3)@SEE_ALSO:valgrind(1)。 LINUX上的“內存泄漏”工具在它能找到的東西中是驚人的! 我強烈建議使用它!

我希望在以后的帖子中發布一個很好的版本“my_company :: unique()”。 現在,它並不完美,因為我希望具有BinaryPredicate的3-arg版本可以無縫地用於函數指針或FUNCTOR,並且我在處理這兩個問題時遇到了一些問題。 如果我無法解決這些問題,我會發布我所擁有的內容,並讓社區改進我到目前為止所做的工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM