C++不像java和c#那樣有垃圾回收機制,是以記憶體洩露是個比較頭疼的問題(誰會忘記寫delete?誰都會!),有幾種緩解之道:
1)智能指針(推薦boost::shared_ptr)。
2)真正的垃圾回收,參考http://www.hpl.hp.com/personal/Hans_Boehm/gc/,這個我沒用過,因為據說不實用:)故本文不讨論。
3)這是從QT中學到的方法,将堆中配置設定的記憶體組成森林,然後删除根結點的時候級聯删除所有子結點。這樣就簡化了記憶體管理:從管理所有記憶體到隻要管理根結點。
先讨論方法1:
對于智能指針來說,如果存在互相引用現象,則會導緻記憶體無法釋放,是以使用時務必要規避這種情況。有人不用智能指針而是自己為配置設定的記憶體管理引用計數,這有點類似于重新造輪子,很多情況下是不必要的。
避免發生互相引用并不那麼困難,隻要遵守這兩條原則:
1)如果可以把兩個類了解成"整體-部分"關系(如一個是容器,一個是元素),則整體類對象可以持有部分類對象的智能指針,部分類對象不能持有整體類對象的智能指針。整體類對象的生命周期必須真包含部分類對象的生命周期。如果部分類對象中需要儲存整體類對象的引用,可以使用原生指針(這是安全的,因為整體類活得久)、引用(推薦,因為使用友善且更安全,用過就知道)或者weak_ptr(比較安全但效率沒那麼高)。
2)除此之外,任何對象不持有其他對象的智能指針,與其他對象協作的唯一方式是通過參數傳入其他對象的智能指針。
但知易行難,沒辦法檢查程式設計時是否遵守了這兩條原則。
再讨論方法3:
這種方法的優點是實作簡單,邏輯清晰,效率高。缺點是對異步和closure的支援不好——鬼知道實際調用的時候,指針指向的那貨是否還健在。加入引用計數?哇靠,那還不如直接用智能指針。
但是當程式中不存在異步調用和closure要求的時候,不失為一種可行的記憶體管理方法——大部分用戶端和基于阻塞IO的服務端一般滿足該條件。即使有異步調用,隻要保證要用到的東東在實際調用前不會被幹掉(比如那些東東是永存的),也可以使用該方法。
實際程式設計中如果想要使用該方法,需要基礎類庫的支援。不然一個個去寫基礎類庫(如vector)的wrapper,實在很繁瑣。是以這個從QT中學到的方法,基本上也隻能在QT中用了。
下面給出方法3的一種簡易實作,CascadeElem類是關鍵(爆簡單),其他都是示例代碼哈。
=================================代碼的分割線=============================================
#include <iostream>
#include <vector>
using namespace std;
class CascadeElem
{
public:
CascadeElem(CascadeElem* parent)
{
if (parent != NULL)
{
parent->children_elem_.push_back(this);
}
}
virtual ~CascadeElem()
{
std::size_t cnt = children_elem_.size();
for (std::size_t i = 0; i < cnt; ++i)
{
delete children_elem_[i];
}
}
private:
std::vector<CascadeElem*> children_elem_;
};
class Child : public CascadeElem
{
public:
Child(CascadeElem* parent, int num)
: CascadeElem(parent)
, num_(num)
{
}
~Child()
{
cout << "~Child(" << num_ << ")" << endl;
}
private:
int num_;
};
class Foo : public CascadeElem
{
public:
Foo(CascadeElem* parent, int num)
: CascadeElem(parent)
, num_(num)
{
for (int i = 0; i < num; ++ i)
{
new Child(this, i);
}
}
~Foo()
{
cout << "~Foo(" << num_ << ")" << endl;
}
private:
int num_;
};
int main(int argc, char* argv[])
{
Foo* root = new Foo(NULL, 0);
Foo* ptr1 = new Foo(root, 1);
Foo* ptr2 = new Foo(ptr1, 2);
Foo* ptr3 = new Foo(ptr2, 3);
Foo* ptr4 = new Foo(root, 4);
delete root;
return 0;
}
=================================結果的分割線=============================================
~Foo(0)
~Foo(1)
~Child(0)
~Foo(2)
~Child(0)
~Child(1)
~Foo(3)
~Child(0)
~Child(1)
~Child(2)
~Foo(4)
~Child(0)
~Child(1)
~Child(2)
~Child(3)
可見所有new的對象都被級聯删除了。