![]() |
Home | Libraries | People | FAQ | More |
Boost.Intrusive associative containers offer
the same interface as STL associative containers. However, STL and TR1 ordered
and unordered simple associative containers (std::set,
std::multiset, std::tr1::unordered_set and std::tr1::unordered_multiset) have some inefficiencies
caused by the interface: the user can only operate with value_type
objects. When using these containers we must use iterator
find(const value_type
&value) to find a value. The same happens in other
functions like equal_range,
lower_bound, upper_bound, etc.
Boost.Intrusive 關聯容器提供了和 STL 關聯容器一樣的接口。但是,STL 和 TR1 的有序及無序簡單關聯容器(std::set,
std::multiset, std::tr1::unordered_set 和 std::tr1::unordered_multiset)因為其接口而顯得有些低效:用戶只能操作 value_type
對象。在使用這些容器時,我們必須用 iterator
find(const value_type
&value) 來查找一個值。對於其它函數也一樣,如 equal_range,
lower_bound, upper_bound, 等等。
However, sometimes the object to be searched is quite expensive to construct:
但是有時候,被查找的對象的構造非常昂貴:
#include <boost/intrusive/set.hpp> #include <boost/intrusive/unordered_set.hpp> #include <cstring> using namespace boost::intrusive; // Hash function for strings 字符串的散列函數
struct StrHasher { std::size_t operator()(const char *str) const {
std::size_t seed = 0; for(; *str; ++str) boost::hash_combine(seed, *str); return seed; } }; class Expensive : public set_base_hook<>, public unordered_set_base_hook<> { std::string key_; // Other members... 其它成員
public: Expensive(const char *key) : key_(key) {} //other expensive initializations... 其它代價昂貴的初始化動作...
const std::string & get_key() const { return key_; } friend bool operator < (const Expensive &a, const Expensive &b) { return a.key_ < b.key_; } friend bool operator == (const Expensive &a, const Expensive &b) { return a.key_ == b.key_; } friend std::size_t hash_value(const Expensive &object) { return StrHasher()(object.get_key().c_str()); } }; // A set and unordered_set that store Expensive objects 保存 Expensive 對象的 set 和 unordered_set
typedef set<Expensive> Set; typedef unordered_set<Expensive> UnorderedSet; // Search functions 查找函數
Expensive *get_from_set(const char* key, Set &set_object) { Set::iterator it = set_object.find(Expensive(key)); if( it == set_object.end() ) return 0; return &*it; } Expensive *get_from_uset(const char* key, UnorderedSet &uset_object) { UnorderedSet::iterator it = uset_object.find(Expensive (key)); if( it == uset_object.end() ) return 0; return &*it; }
Expensive is an expensive
object to construct. If "key" c-string is quite long Expensive has to construct a std::string
using heap memory. Like Expensive,
many times the only member taking part in ordering issues is just a small
part of the class. For example, with Expensive,
only the internal std::string is needed to compare the object.Expensive 是一個構造代價昂貴的對象。如果c字符串 "key" 非常長,則 Expensive 必須使用堆內存來構造一個 std::string。像 Expensive 這樣,很多時候參與排序的只是類的一小部分。例如,對於 Expensive,比較對像時只需要其內部的 std::string。
In both containers, if we call get_from_set/get_from_unordered_set
in a loop, we might get a performance penalty, because we are forced to create
a whole Expensive object
to be able to find an equivalent one.
在這兩個容器中,如果我們在一個循環中調用 get_from_set/get_from_unordered_set,我們就有可能遇到性能的問題,因為我們被迫創建整個 Expensive 對像才能查找相等的元素。
Sometimes this interface limitation is severe, because we might
not have enough information to construct the object but we might
have enough information to find the object.
In this case, a name is enough to search Expensive
in the container but constructing an Expensive
might require more information that the user might not have.
有時候,這種接口的限制是嚴重的,因為我們可能沒有足夠的信息來構造這個對象,不過我們可能有足夠的信息來找到這個對象。在這種情況下,有一個名字就足以在容器中查找 Expensive,而構造一個 Expensive
則可能需要用戶也許沒有的信息。
To solve this, set/multiset offer alternative functions,
which take any type comparable with the value and a functor that should be
compatible with the ordering function of the associative container. unordered_set/unordered_multiset
offers functions that take any key type and compatible hash and equality
functions. Now, let's see the optimized search function:
為了解決這一問題,set/multiset 提供了替代的函數,它接受可以與元素值相比較的任何類型,以及一個兼容於關聯容器的排序函數的仿函數。unordered_set/unordered_multiset 則提供了接受任何關鍵字類型的函數,以及兼容的散列函數和相等性函數。現在,我們來看一看優化後的查找函數:
// These compare Expensive and a c-string 將 Expensive 與一個c字符串進行比較
struct StrExpComp { bool operator()(const char *str, const Expensive &c) const { return std::strcmp(str, c.get_key().c_str()) < 0; } bool operator()(const Expensive &c, const char *str) const { return std::strcmp(c.get_key().c_str(), str) < 0; } }; struct StrExpEqual { bool operator()(const char *str, const Expensive &c) const { return std::strcmp(str, c.get_key().c_str()) == 0; } bool operator()(const Expensive &c, const char *str) const { return std::strcmp(c.get_key().c_str(), str) == 0; } }; // Optimized search functions 優化後的查找函數
Expensive *get_from_set_optimized(const char* key, Set &set_object) { Set::iterator it = set_object.find(key, StrExpComp()); if( it == set_object.end() ) return 0; return &*it; } Expensive *get_from_uset_optimized(const char* key, UnorderedSet &uset_object) { UnorderedSet::iterator it = uset_object.find(key, StrHasher(), StrExpEqual()); if( it == uset_object.end() ) return 0; return &*it; }
This new arbitrary key overload is also available for other functions taking
values as arguments:
這個新的任意鍵的重載對於其它以元素值作為參數的函數也可用:
Check set, multiset, unordered_set,
unordered_multiset
references to know more about those functions.
請查閱 set, multiset, unordered_set,
unordered_multiset 的參考手冊,瞭解有關這些函數的更多信息。
A similar issue happens with insertions in simple ordered and unordered associative
containers with unique keys (std::set and
std::tr1::unordered_set).
In these containers, if a value is already present, the value to be inserted
is discarded. With expensive values, if the value is already present, we
can suffer efficiency problems.
在往簡單的具有唯一鍵的有序和無序關聯容器(std::set 和
std::tr1::unordered_set)中進行插入時也有類似問題。在這些容器中,如果一個值已經存在,則被插入的值將被丟棄。對於代價昂貴的值,如果值已經存在,則我們會經受效率的問題。
set and unordered_set
have insertion functions to check efficiently, without constructing the value,
if a value is present or not and if it's not present, a function to insert
it immediately without any further lookup. For example, using the same Expensive class, this function can be inefficient:set 和 unordered_set
具有無須構造插入值即可高效檢查該值是否存在的插入函數,如果該值不存在,則另一個函數立即將它插入,不需要任何多餘的查找。例如,使用相同的 Expensive 類,以下函數是低效的:
// Insertion functions 插入函數
bool insert_to_set(const char* key, Set &set_object) { Expensive *pobject = new Expensive(key); bool success = set_object.insert(*pobject).second; if(!success) delete pobject; return success; } bool insert_to_uset(const char* key, UnorderedSet &uset_object) { Expensive *pobject = new Expensive(key); bool success = uset_object.insert(*pobject).second; if(!success) delete pobject; return success; }
If the object is already present, we are constructing an Expensive
that will be discarded, and this is a waste of resources. Instead of that,
let's use insert_check and
insert_commit functions:
如果對像已存在,那麼我們就構造了一個將被丟棄的 Expensive,這是資源的浪費。相反,我們來使用 insert_check 和
insert_commit 函數:
// Optimized insertion functions 優化的插入函數
bool insert_to_set_optimized(const char* key, Set &set_object) { Set::insert_commit_data insert_data; bool success = set_object.insert_check(key, StrExpComp(), insert_data).second; if(success) set_object.insert_commit(*new Expensive(key), insert_data); return success; } bool insert_to_uset_optimized(const char* key, UnorderedSet &uset_object) { UnorderedSet::insert_commit_data insert_data; bool success = uset_object.insert_check (key, StrHasher(), StrExpEqual(), insert_data).second; if(success) uset_object.insert_commit(*new Expensive(key), insert_data); return success; }
insert_check is similar to
a normal insert but:insert_check 類似於普通的 insert,不過:
insert_check can be used
with arbitrary keysinsert_check 可以使用任意鍵
insert_check collects all the needed
information in an insert_commit_data
structure, so that insert_commit:如果插入是可以的(沒有相同的值),則 insert_check 將所有需要的信息收集到一個 insert_commit_data
結構中,這樣 insert_commit:
可以以常數時間複雜度執行。
具有無拋出保證。
These functions must be used with care, since no other insertion or erasure
must be executed between an insert_check
and an insert_commit pair.
Otherwise, the behaviour is undefined. insert_check
and insert_commit will come
in handy for developers programming efficient non-intrusive associative containers.
See set and unordered_set reference
for more information about insert_check
and insert_commit.
這些函數必須小心使用,因為在一對 insert_check 和 insert_commit 之間必須不能有其它插入或刪除。否則,行為將是未定義的。insert_check 和 insert_commit 對於編寫高效的非介入式關聯容器也能派上用場。有關 insert_check 和 insert_commit 的更多信息,請見 set 和 unordered_set 的參考手冊。
With multiple ordered and unordered associative containers (multiset
and unordered_multiset)
there's no need for these advanced insertion functions, since insertions
are always succesful.
對於非唯一的有序和無序關聯容器(multiset 和 unordered_multiset),不需要這些插入函數,因為插入操作總是成功的。
For more information about advanced lookup and insertion functions see set, multiset,
unordered_set
and unordered_multiset
references.
有關高級查找和插入函數的更多信息,請見 set, multiset,
unordered_set 和 unordered_multiset 的參考手冊。