文件位於 llvm/include/llvm/[[ADT]]/FoldingSet.hhtml
fold: 摺疊。node
[[FoldingSet]] 對於昂貴建立或多態的對象是一個好的集合類。參見:http://llvm.org/docs/ProgrammersManual.html#dss_FoldingSet算法
== 定義的類或方法 ==
* FoldingSetImpl -- 實現 FoldingSet 的功能。主要結構是桶的數組。
* FoldingSetTrait -- 定義如何記錄(profile)指定類型的對象。
* DefaultFoldingSetTrait -- FoldingSetTrait 的缺省實現。
* ContextualFoldingSetTrait --
* DefaultContextualFoldingSetTrait -- 上面類的缺省實現。
* FoldingSetNodeIDRef -- 描述到 FoldingSetNodeID 的引用。
* FoldingSetNodeID -- 收集一個節點的全部惟一數據位。
* FoldingSetNode
* FoldingSet
* ContextualFoldingSet
* FoldingSetIteratorImpl -- FoldingSetIterator 公共部分。
* FoldingSetIterator
* FoldingSetBucketIteratorImpl -- FoldingSetBucketIterator 公共部分。
* FoldingSetBucketIterator
* FoldingSetNodeWrapper
* FastFoldingSetNode -- FoldingSetNode 的子類,其保存 FoldingSetNodeID 信息而不是每次都調用 node 重複計算。即空間換時間。
* FoldingSetTrait<T*> -- FoldingSetTrait 對指針的特化。數組
== 模板類 DefaultFoldingSetTrait ==
DefaultFoldingSetTrait - 提供 FoldingSetTrait 的缺省實現。類概要:
<syntaxhighlight lang="cpp">
template<T> struct DefaultFoldingSetTrait {
Profile(T &X, FoldingSetNodeID &ID); // 要求 X 提供方法 X.Profile(ID) 完成此 Profile() 請求。
Equals(X, ID) // 測試是否 X.profile == ID。缺省實現也許不夠效率,派生類能夠 override 此實現。
ComputeHash(X) // 計算 X 的哈希值。派生類能夠提供更優化的實現。
}
</syntaxhighlight>數據結構
也即,這個 Trait 類提供 T 的 Profile,Equals,ComputeHash 的缺省實現。爲實現,缺省要求 T 提供 Profile 方法,Equals,ComputeHash 缺省使用 Profile 進行計算。app
== FoldingSetTrait ==
該模板類 FoldingSetTrait<T> 從 DefaultFoldingSetTrait<T> 繼承,本身沒有任何額外的方法和數據。咱們認爲這是爲了留給 T 作模板特化用的。配合 FoldingSetNodeWrapper 類,咱們能夠給對象添加原來並未設計,而 FoldingSet 所需的 Profile 方法。ide
== 模板類 DefaultContextualFoldingSetTrait ==
* contextual: 環境的,上下文的,先後有關的。函數
相似於 DefaultFoldingSetTrait,但提供 ContextualFoldingSetTrait 的缺省實現。
<syntaxhighlight lang="cpp">
template<T, CTXT> // CTXT: 環境,上下文
struct DefaultContextualFoldingSetTrait {
Profile(T &X, FoldingSetNodeID &ID, CTXT) // 要求 X 實現 X.Profile(ID,CTXT)
Equals(...,CTXT); // 帶有 CTXT 的 Equals()
ComputeHash(...,CTXT) // 帶有 CTXT 的 ComputeHash()
};
</syntaxhighlight>學習
這個類與 DefaultFoldingSetTrait 的區別在於多了一個 CTXT 模板參數,並在全部函數中有 CTXT 類型的參數。(但不是 CTXT& 類型的參數,奇怪嗎?)測試
== 模板類 ContextualFoldingSetTrait ==
相似於 FoldingSetTrait,但以 DefaultContextualFoldingSetTrait 爲基本實現。留給類型 T, CTXT 作特化用。
== 類 FoldingSetNodeID ==
FoldingSetNodeID - 這個類用於收集一個節點(node)的全部惟一數據位(作爲惟一 Key)。當全部數據都收集到以後,爲此節點產生一個 hash 值。
<syntaxhighlight lang="cpp">
class FoldingSetNodeID {
SmallVector<unsigned, 32> Bits; // 缺省使用 SmallVector 來存放惟一數據。
this() // 缺省構造,和帶參數構造。參見 FoldingSetNodeIDRef
AddXXX(T) // 支持多種 T 類型的 AddXXX() 方法。包括指針,整數,字符串等。
ComputeHash() // 計算此實例的 hash 值,用於在 FoldingSet 中查找節點。
operator==() // 比較。
Intern() // 參見 FoldingSetNodeIDRef 的說明。
}
</syntaxhighlight>
當前我推測,這個類用於幫助收集指定對象 obj 的可區分於別的 obj 的數據,也便可以認爲是產生 obj 的惟一 Key (Primary Key, Unique Key)。各類 AddXXX() 方法加入的數據要足以區分這個 obj。下面研究典型幾個的 Add() 函數:
* AddPointer(const void *Ptr) -- Ptr 當作整數 append 到 Bits 末尾。
* AddInteger(unsigned I) -- 將 I append 到 Bits 末尾。
* AddInteger(integer) -- 相似,根據 integer 類型大小,加入的 1~2 次 unsigned 值。
* AddString() -- 先加入大小,而後加入字符串到 Bits.
* 其它的相似,總之是構建一個 vector<unsigned> Bits,用以惟一化指定的 obj。也可認爲是將 obj 給 serialize() 了。。。
* ComputeHash():根據 Bits 中的全部已加入的數據,計算 hash 值。
這個對象的 sizeof() = 144 (不一樣機器可能有不一樣),通常應是引用傳遞。裏面的 [[SmallVector]] 若是裝不下了,可能會在堆中分配空間的。
== 類 FoldingSetNodeIDRef ==
FoldingSetNodeIDRef 該類引用到保留(interned)起來的 FoldingSetNodeID,底層數據保存在某個分配器分配的堆中。通常不保存 FoldingSetNodeID 自身。
<syntaxhighlight lang="cpp">
class FoldingSetNodeIDRef {
unsigned *Data; // 指向底層數組
unsigned Size; // 數組長度
this() // 構造,使用指定 Data,Size 構造。
ComputeHash() // 爲 Data,Size 計算 hash 值。
operator==() // 進行 == 判斷。
}
</syntaxhighlight>
* 在 FoldingSetNodeID 中的方法 Intern(BumpPtrAllocator &alloc),從 alloc 中分配空間保存 Data,而後用 Data,Size 構造 FoldingSetNodeIDRef 並返回。 alloc 負責管理分配了的內存。參見 [[BumpPtrAllocator]] 的說明。
== FoldingSetImpl ==
類 FoldingSetImpl 實現 folding set 的功能。主要的數據結構是一個桶(buckets)的數組。每一個桶經過節點的 hash 值進行索引。
=== FoldingSetImpl::Node ===
這個類的定義包含在 FoldingSetImpl 裏面,爲方便,拿出來單獨寫。
該類用於保存單向連接的列表節點。
<syntaxhighlight lang="cpp">
class FoldingSetImpl::Node {
void *NextBucket; // bucket 列表中的 next 連接。
// 構造,
get|setNextInBucket() // 獲取/設置鏈表中下一個 Node.
}
</syntaxhighlight>
在 FoldingSet 集合中的節點被要求從這個類繼承,也即須要有一個單連接指針指向下一個節點。(侵入式單鏈表)這裏沒有再使用複雜的模板 Traits,特化機制等等。
=== FoldingSetImpl ===
這個類實現了 FoldingSet 的基本功能。主要的數據結構是 Buckets 數組。經過 Node 的哈希值索引,其中的節點使用 Node.next 單向連接在一塊兒。最後一個節點的 next 指針指向 bucket,這樣便於節點刪除操做。
<syntaxhighlight lang="cpp">
class FoldingSetImpl {
void **Buckets; // 桶的數組。
unsigned NumBuckets; // 桶的數量,也即數組大小。
unsigned NumNodes; // 當前集合中節點的數量。數量不少時候會增加總的桶數。
this(), virtual ~() // 構造與虛析構(爲何是虛的?)。
RemoveNode(Node *) // 從集合中刪除指定節點。
GetOrInsertNode(Node *) // 得到或插入節點,若是已經有了則返回,不然插入。
FindNodeOrInsertPos(ID, &InsertPos) // 查找ID的節點。不存在則返回插入點。
InsertNode(Node *) // 插入節點。
size(),empty() // 容器標準方法。
// 派生類必須實現的虛方法,和 FoldingSetTrait 類的方法一致。
virtual GetNodeProfile(), NodeEquals(), ComputeNodeHash()
}
</syntaxhighlight>
* 問題:爲何析構是虛函數?爲何 GetNodeProfile() 等也被定義爲虛函數呢?
* 咱們稍後在學習 FoldingSet 具體實現的時候,再看看是否須要學習幾個重要函數的實現機制。
== 模板類 FoldingSet ==
FoldingSet<T> 模板類用於實現對特定的模板參數 T 實現的 FoldingSet。類 T 必須從 FoldingSetImpl::Node 類繼承,並實現 Profile() 函數。
<syntaxhighlight lang="cpp">
template <T> class FoldingSet : public FoldingSetImpl {
virtual GetNodeProfile() // 實現父類的虛函數,轉換節點爲一個 ID
virtual NodeEquals() // 使用 Trait 類比較 Node 是否相等
virtual ComputeNodeHash() // 計算節點的哈希值
this(Log2InitSize=6) // 構造。桶的初始數量爲 2 的 Log2InitSize 冪次。2**6 = 32.
iterator 實現爲 FoldingSetIterator<T>, const_iterator ...
begin(),end()
bucket_iterator 實現爲 FoldingSetBucketIterator<T>
bucket_begin,bucket_end()
GetOrInsertNode(Node *N) // 獲取或插入節點
FindNodeOrInsertPos(ID, InsertPos) // 查找或提供插入點。
}
</syntaxhighlight>
* FoldingSet 的基本功能實如今 FoldingSetImpl 中,FoldingSet 對類型作了簡單封裝。爲此須要看 FoldingSetImpl 的函數實現,以理解其實現機理。
== FoldingSetImpl 和 FoldingSet 實現機理 ==
爲研究 FoldingSet 實現機理,咱們看幾個主要函數的實現,及其要點。
=== GetOrInsertNode ===
函數原型爲 Node *GetOrInsertNode(Node *N),其做用是,若是已經有一個節點等於(Trait::Equals)指定的節點,則返回已有的;不然,插入 N 到集合中而後返回。
僞代碼以下:
<syntaxhighlight lang="cpp">
Node *FS::GetOrInsertNode(Node *N) { // F 是 FoldingSetImpl 簡寫
ID = GetNodeProfile(N) // 獲得節點 N 的惟一標識 ID。
Node *E, InsertPos *I = FindNodeOrInsertPos(ID) // 查找這個節點,若是沒有則返回插入點
if (E) // 標識爲 ID 的節點存在
return E // 則返回已經存在的這個節點
InsertNode(N, I) // 將節點 N 插入到位置 I
return N // 返回插入的節點。
}
</syntaxhighlight>
* GetNodeProfile(N) 僞代碼獲得節點 N 的惟一標識,結果爲 FoldingSetNodeID ID
* FindNodeOrInsertPos(ID) 下面研究
* InsertNode(N,I) 下面研究
這個函數能夠認爲是兩個子功能合併在一塊兒:GetNode(N.ID), InsertNode(N)
=== FindNodeOrInsertPos(ID) ===
查找指定 ID 的節點,若是存在則返回;不然得到插入的位置。這個函數能夠認爲是兩個子功能的合併:FindNode(ID), CalcInsertPos(ID)。
僞代碼以下:
<syntaxhighlight lang="cpp">
Node *FS:FindNodeOrInsertPos(ID, InsertPos &I) { // I 用於作返回值。
hash = ID.ComputeHash() // 計算 ID 對應的哈希值
Bucket = GetBucketFor(hash) // 獲得 ID 所在的桶(Bucket)
while (Node *n = get_next_node()) // 循環:從桶中獲得下一個節點。是單鏈表。
if (n->ID == ID) // 若是節點 n 的 ID 與參數 ID 相同,則爲找到了。
return n // 找到了,返回
// 沒找到,返回空指針、插入位置。其實,就算找到了,也能夠返回插入位置的。。。
InsertPos &I = Bucket
return (Node *)null
}
</syntaxhighlight>
* 注:爲節省空間,桶中的節點使用了單鏈表連接在一塊兒,最後一個節點指回 Bucket 自身。get_next_node() 利用指針的最低位 bit (Bit0) 置位表示指回了 Bucket。參見函數 FoldingSetImpl::GetNextPtr() 及其說明。
=== InsertNode ===
InsertNode 有兩種形式,其中 InsertNode(Node*) 的調用形式轉換爲對 InsertNode(Node*, InsertPos I) 的調用,因此這裏主要研究後一種形式。InsertPos 經過 FindNodeOrInsertPos() 方法獲得。
<syntaxhighlight lang="cpp">
void FS::InsertNode(Node *N, InsertPos I) {
// 若是元素數量太多,則增大哈希表桶的總數。
Grow() if (NumNodes > 2*NumBuckets)
Bucket *= I // 插入點 I 實際上就是 Bucket 桶的指針。
N->SetNextInBucket(Next) // 設置 N 的 next 指針。具體 Next 算法見程序
*Bucket = N // 新節點插入到桶中節點列表的第一個。
}
</syntaxhighlight>
須要注意的是,Next 若是指回 Bucket 本身,則設置其指針的最後 bit 爲 1。
=== RemoveNode ===
從 FoldingSet 中刪除一個節點。返回 true 若是存在並刪除成功;返回 false 表示不存在。
<syntaxhighlight lang="cpp">
bool FS:RemoveNode(Node *N) {
*next = N->getNextInBucket() // 獲得 N 的 next 指針,若是爲 null 表示不在 FS 中。
若是 next == null 表示不在 FS 中,則返回 return false
...
while () {
// 在單鏈表 next->next->... 中查找,直到碰到指向 N 的那個節點,
// 將其 next 指向 N 的next(修改單鏈表) 返回 true
// 或者發現桶的指針須要更新,則更新。返回 true
}
}
</syntaxhighlight>
這裏使用了循環的單鏈表來尋找 next 指針及進行修改。若是不使用循環,則須要計算 N 的 ID,而後找到 Bucket,進行單鏈表的搜索。
綜上所述,FoldingSet 上面各個函數實現了一個哈希表,解決衝突用的是鏈地址法。
其實,使用標準 hash_map,對 key,value 進行一些特殊處理,是否是也能解決需求呢?