[英]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_ptr
和std::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>>
。 無論如何,我們希望共享,它足夠快,直到分析說不然,它是安全的,並且它易於使用。
我假設如下:
以下事情浮現在我的腦海中:
使用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.