在 C++ 中实现一个轻量的标记清除 gc 系统
在 C++ 中實現一個輕量的標記清除 gc 系統
最近想把 engine 做一個簡單 C++ 封裝,結合 QT 使用。engine 本身是用純 C 實現的,大部分應用基于 lua 開發。對對象生命期管理也依賴 lua 的 gc 系統。關于這部分的設計,可以參考我以前寫的一篇 為 lua 封裝 C 對象的生存期管理問題 。
當我們把中間層搬到 C++ 中時,遇到的問題之一就是,C++ 沒有原生的 gc 支持。我也曾經寫過一個 gc 庫。但在特定應用下還不夠簡潔。這幾天過年休息,仔細考慮了一下相關的需求,嘗試實現了一個更簡單的 gc 框架。不到 200 行代碼吧,我直接列在這篇 blog 里。
這些尚是一些玩具代碼,我花了一天時間來寫。有許多考慮不周的地方,以及不完整的功能。但可以闡明一些基本思路。
首先我需要一個標記清除的 gc 系統,用來解決引用記數不容易解決的循環引用問題。它的實現不想比引用記數復雜太多,并有相同甚至更高的性能。
我不想使用復雜的 template 技術,利用太多的語法糖讓使用看起來簡單。如果需要讓這些 C++ 代碼看起來更現代,更有“品味”,我想也不是很難的事情。
接口和實現希望盡量分離,對用的人少暴露細節。但不拘泥于教條,強求做成類似 COM 那樣的通用 ABI 。還是盡量利用 C++ 語言本身提供的機制,不濫用。
使用盡量簡單,不要讓使用人員有太大負擔。
功能滿足最低需求即可。代碼容易閱讀,使用人員可以很快理解原理,不至于誤用。也方便日后擴展以適應新的需求。
代碼如下:(可打包下載)
/*
?*? filename:? i_gcobject.h
?*? Copyright (c) 2010 ,
?*????? Cloud Wu . All rights reserved.
?*
?*????? http://www.codingnow.com
?*
?*? Use, modification and distribution are subject to the "New BSD License"
?*? as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.
?*/
?
#ifndef interfacce_gcobject_h
#define interfacce_gcobject_h
?
#define interface struct
?
interface i_gcobject {
??? virtual ~i_gcobject() {}
??? virtual void touch() {}
??? virtual void mark() = 0 ;
??? virtual void grab() = 0 ;
??? virtual void release() = 0 ;
?
??? static void collect();
};
?
#endif
所有支持 gc 管理的接口都繼承至 i_gcobject ,提供三個方法,
1.??? mark 可以把這個對象打上標記,被標記的對象將不會被 collect 回收。
2.??? grab 將對象掛接到一個被稱呼為 root 的特殊 gcobject 上。
3.??? release 將對象從 root 上取掉。
另提供 touch 的模板方法供 mark 回調,用來標記同一對象中的不同部分。
mark 方法一般在 touch 方法中使用,另外,collect 方法將主動調用 root 的 mark 。
/*
?*? filename:? i_gcholder.h
?*? Copyright (c) 2010 ,
?*????? Cloud Wu . All rights reserved.
?*
?*????? http://www.codingnow.com
?*
?*? Use, modification and distribution are subject to the "New BSD License"
?*? as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.
?*/
?
#ifndef interfacce_gcholder_h
#define interfacce_gcholder_h
?
#include "i_gcobject.h"
?
interface i_gcholder : virtual i_gcobject {
??? virtual void hold(i_gcobject *) = 0;
??? virtual void unhold(i_gcobject *) = 0;
?
??? static i_gcholder * create();
};
?
#endif
i_gcholder 為 root 的接口,提供 hold 和 unhold 方法來掛接需要持久保留的 gcobject 。
/*
?*? filename:? gcobject.h
?*? Copyright (c) 2010 ,
?*????? Cloud Wu . All rights reserved.
?*
?*?? ???http://www.codingnow.com
?*
?*? Use, modification and distribution are subject to the "New BSD License"
?*? as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.
?*/
?
#ifndef gc_object_h
#define gc_object_h
?
#include "i_gcobject.h"
?
class gcobject : virtual i_gcobject {
??? bool marked;
public:
??? gcobject();
??? virtual void mark();
??? virtual void grab();
??? virtual void release();
??? struct f_unmarked;
};
?
#endif
/*
?*? filename:? gcobject.cpp
?*? Copyright (c) 2010 ,
?*????? Cloud Wu . All rights reserved.
?*
?*????? http://www.codingnow.com
?*
?*? Use, modification and distribution are subject to the "New BSD License"
?*? as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.
?*/
?
#include "gcobject.h"
#include "i_gcholder.h"
?
#include <vector>
#include <algorithm>
?
static bool gc_trigger;
static std::vector<gcobject *> gc_pool;
static i_gcholder * gc_root = i_gcholder::create();
?
struct gcobject::f_unmarked {
??? bool operator() (gcobject * value) {
??? ????bool unmarked = value->marked != gc_trigger;
??????? if (unmarked) {
??????????? delete value;
??????? }
??????? return unmarked;
??? }
};
?
gcobject::gcobject() : marked(!gc_trigger)
{
??? gc_pool.push_back(this);
}
?
void
gcobject::mark()
{
??? if (marked != gc_trigger) {
??????? marked = gc_trigger;
??????? touch();
??? }
}
?
void
gcobject::grab()
{
??? gc_root->hold(this);
}
?
void
gcobject::release()
{
??? gc_root->unhold(this);
}
?
void
i_gcobject::collect()
{
??? gc_root->mark();
?
??? gc_pool.erase(remove_if(gc_pool.begin(), gc_pool.end() , gcobject::f_unmarked()), gc_pool.end());
?
??? gc_trigger = !gc_trigger;
}
gcobject 為具體的 gc 實現,實現了 mark 、grab、release 和 collect 方法。
1.??? mark 采用的直接向一 bool 變量設置標記。這個標記利用了 trigger 這個乒乓開關,每次 collect 都會切換狀態。
2.??? grab 和 release 可以把對象掛接到 root 上,或從上取掉。
3.??? collect 會主動從 root 開始 mark ,并釋放那些沒有 mark 的對象。
/*
?*? Copyright (c) 2010 ,
?*????? Cloud Wu . All rights reserved.
?*
?*????? http://www.codingnow.com
?*
?*? Use, modification and distribution are subject to the "New BSD License"
?*? as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.
?*/
?
#include "i_gcholder.h"
#include "gcobject.h"
#include <vector>
#include <algorithm>
#include <cassert>
?
class gcholder : public virtual i_gcholder, virtual gcobject {
??? std::vector<i_gcobject *> hold_set;
??? std::vector<i_gcobject *> unhold_set;
??? bool set_changed;
??? bool hold_set_sorted;
??? bool unhold_set_sorted;
??? void combine_set();
??? virtual void touch();
??? virtual void hold(i_gcobject *obj) {
??????? hold_set.push_back(obj);
??????? hold_set_sorted = false;
??????? set_changed = true;
??? }
??? virtual void unhold(i_gcobject *obj) {
??????? unhold_set.push_back(obj);
??????? unhold_set_sorted = false;
??????? set_changed = true;
??? }
??? struct f_mark {
??????? void operator() (i_gcobject *obj) {
??????????? obj->mark();
??????? }
??? };
public:
??? gcholder() :
??????? set_changed(false),
??????? hold_set_sorted(true) ,
??????? unhold_set_sorted(true) {}
};
?
void
gcholder::combine_set()
{
??? if (!hold_set_sorted) {
??????? std::sort(hold_set.begin(),hold_set.end());
??????? hold_set_sorted = true;
??? }
??? if (!unhold_set_sorted) {
??????? std::sort(unhold_set.begin(),unhold_set.end());
??????? unhold_set_sorted = true;
??? }
??? if (!unhold_set.empty()) {
??????? std::vector<i_gcobject *>::iterator iter1 = hold_set.begin();
??????? std::vector<i_gcobject *>::iterator iter2 = unhold_set.begin();
??????? while (iter1 != hold_set.end() && iter2 != unhold_set.end()) {
??????????? if (*iter1 == *iter2) {
??????????????? *iter1 = NULL;
??????????????? ++iter1;
??????????????? ++iter2;
??????????? }
??????????? else {
??????????????? assert(*iter1 < *iter2);
??????????????? ++iter1;
??????????? }
??????? }
??????? i_gcobject * null = NULL;
??????? hold_set.erase(std::remove(hold_set.begin(),hold_set.end(),null) , hold_set.end());
??????? unhold_set.clear();
??? }
}
?
void
gcholder::touch()
{
??? if (set_changed) {
??????? combine_set();
??????? set_changed = false;
??? }
?
??? std::for_each(hold_set.begin(), hold_set.end(), f_mark());
}
?
i_gcholder *
i_gcholder::create()
{
??? return new gcholder;
}
gcholder 理論上可以有多個實例,并相互掛接。(否則不需要繼承至 i_gcobject )這個設計可以用來模擬多級的堆棧。但實際上并不需要這么復雜。因為在大部分應用里,如果你的程序有一個周期性的主循環,就可以不在 gc 系統里模擬出一個多級的堆棧。我們只用在循環之外做 collect 即可。再堆棧調用的較深層次觸發 collect 反而效果不佳,會導致許多臨時 gc 對象無法回收。
最后來看一個玩具代碼,用 stl 里的 mutliset 實現了一個簡單的樹接口。可能沒有什么使用價值,但它演示了一個較復雜的對象相互引用的關系。并可以展示 gc 如何正確工作。
/*
?*? filename:? test.cpp
?*? Copyright (c) 2010 ,
?*????? Cloud Wu . All rights reserved.
?*
?*????? http://www.codingnow.com
?*
?*? Use, modification and distribution are subject to the "New BSD License"
?*? as listed at <url: http://www.opensource.org/licenses/bsd-license.php >.
?*/
?
#include "gcobject.h"
?
#include <cstdio>
#include <set>
#include <algorithm>
?
interface i_tree : virtual i_gcobject {
??? virtual void link(i_tree *p) = 0;
?
??? static i_tree * create();
};
?
class tree : public virtual i_tree , virtual gcobject {
??? tree *parent;
??? std::multiset<tree *> children;
?
??? struct f_mark {
??????? void operator() (tree *node) {
??????????? node->mark();
??????? }
??? };
?
??? virtual void touch() {
??????? if (parent)
??????????? parent->mark();
?
??????? std::for_each(children.begin(), children.end(), f_mark());
??? }
?
??? void unlink();
?
??? virtual void link(i_tree *parent);
public:
??? tree() : parent(NULL) {
??????? printf("create node %p\n",this);
??? }
?? ?~tree() {
??????? printf("release node %p\n",this);
??? }
};
?
void
tree::unlink()
{
??? if (parent) {
??????? parent->children.erase(this);
?
??????? parent = NULL;
??? }
}
?
void
tree::link(i_tree *p)
{
??? unlink();
??? if (p) {
??????? tree * cp = dynamic_cast<tree *>(p);
??????? cp->children.insert(this);
??????? parent = cp;
??? }
}
?
i_tree *
i_tree::create()
{
??? return new tree;
}
?
int
main()
{
??? i_tree *root = i_tree::create();
?
??? root->grab();
?
??? i_tree *node;
?
??? node = i_tree::create();
??? node->link(root);
?
??? node = i_tree::create();
??? node->link(root);
?
??? i_gcobject::collect();
?
??? printf("collected\n");
??? node->link(NULL);
?
??? i_gcobject::collect();
?
??? printf("finalize\n");
?
??? root->release();
?
??? i_gcobject::collect();
?
??? return 0;
}
我們在實現一個基于 gc 的對象時,可以先定義出需要的接口,讓接口從 i_gcobject 繼承。例如上例中的 i_tree 。
然后在實現這個接口時,可以虛繼承 gcobject 。例如上例中的 tree 。
如果有需要,就重載 touch 方法,在 touch 方法中 mark 相關的 gcobject 。對于 tree 這個例子,就是調用父親和孩子節點的 mark 。
對象依然可以寫析構函數,相當于對象的 finalize 。在析構函數中,不要再釋放和它相關的 gcobject ,那些留給 gc 系統去完成。(例如在 tree 里,就不要在 ~tree 中 delete children 容器中的變量,也不需要把自己從父親節點上摘掉)
如果僅僅只是使用那些接口,則不需要再包含 gcobject.h ,因為 gcobject 的細節只供實現 i_gcobject 時使用。
?
總結
以上是生活随笔為你收集整理的在 C++ 中实现一个轻量的标记清除 gc 系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: wed6699整站程序下载【首发】
- 下一篇: C语言中的数组排序