Boost.Intrusive также предлагает хешированные контейнеры, которые могут быть очень полезны для реализации контейнеров быстрого просмотра. Эти контейнеры (unordered_set
и unordered_multiset
) являются полуинтрузивными контейнерами: им нужна дополнительная память, кроме крючка, хранящегося в value_type
. Эта дополнительная память должна быть передана в конструкторе контейнера.
В отличие от неупорядоченных ассоциативных контейнеров C++ TR1 (которые также являются хешированными контейнерами), содержимое этих полуинтрузивных контейнеров не восстанавливается для поддержания коэффициента нагрузки: для этого потребуется управление памятью, а навязчивые контейнеры вообще не реализуют никакого управления памятью. Однако пользователь может запросить явную перетасовку, пропустив новый массив ведра. Это также дает дополнительную гарантию над неупорядоченными ассоциативными контейнерами TR1: при вставке элемента в контейнер не признаются недействительными.
Как и в случае неупорядоченных ассоциативных контейнеров TR1, перетасовка обесценивает итераторы, изменяет порядок между элементами и изменения, в которых появляются элементы ведер, но не обесценивает указатели или ссылки на элементы.
Помимо ожидаемых объектов хеш-функции и функции равенства, Boost.Intrusive Конструкторы неупорядоченных ассоциативных контейнеров используют аргумент, определяющий вектор вспомогательного ковша, который будет использоваться контейнером.
Размер накладных расходов для хешированного контейнера является умеренным: 1 указатель на значение плюс массив ведра на контейнер. Размер элемента массива ковша обычно является одним указателем. Для получения хешированного контейнера с хорошей производительностью длина ведра обычно совпадает с количеством элементов, содержащихся в контейнере, поэтому хорошо сбалансированный хешированный контейнер (bucket_count()
равен size()
) будет иметь эквивалентные накладные расходы в размере двух указателей на элемент.
Пустой, непостоянный размер неупорядоченный_set
или неупорядоченный_multiset
также имеет размер bucket_count()
указателей.
Вставки, стирания и поиски амортизировали сложность в постоянное время в хешированных контейнерах. Однако некоторые наихудшие гарантии являются линейными. См. unordered_set
или unordered_multiset
для гарантий сложности каждой операции.
Будьте осторожны с хешированными контейнерами с непостоянным размером времени: некоторые операции, такие как пустые()
, имеют линейную сложность, в отличие от других контейнеров Boost.Intrusive.
unordered_set
и unordered_multiset
используют одни и те же крючки. Это преимущество, поскольку один и тот же тип пользователя может быть вставлен сначала в неупорядоченный_multiset
, а затем в неупорядоченный_set
без изменения определения класса пользователя.
template <class ...Options>
class unordered_set_base_hook;
template <class ...Options>
class unordered_set_member_hook;
unordered_set_base_hook
и unordered_set_member_hook
получают те же опции, что и в разделе Как использовать Boost.Intrusive:
tag<class Tag>
(только для базовых крючков): Этот аргумент служит тегом, поэтому вы можете получить более одного базового крюка. По умолчанию: tag<default_tag>
.
link_mode<link_mode_type LinkMode>
: Политика ссылок. По умолчанию: link_mode<safe_link>
.
void_pointer<class VoidPointer>
: Тип указателя, который должен использоваться внутри крючка и распространяться на контейнер. По умолчанию: void_pointer<void>
.
Помимо них, эти крючки предлагают дополнительные варианты:
store_hash<bool Enabled>
: Эта опция оставляет дополнительное пространство в крючке для хранения хеш-значения объекта после его введения в контейнер. При использовании этой опции неупорядоченный контейнер будет хранить вычисленное значение хэша в крючке, и операциям перетаскивания не нужно будет пересчитывать значение хэша. Этот вариант улучшит производительность неупорядоченных контейнеров при частой перетасовке или медленной операции хеширования. По умолчанию: store_hash<false>
.
optimize_multikey<bool Enabled>
: Эта опция оставляет дополнительное пространство в крючке, которое будет использоваться для группировки равных элементов в неупорядоченных мультисетах, значительно улучшая производительность при вставке в эти контейнеры многих равных значений. По умолчанию: оптимизировать_multikey<ложно>
.
template<class T, class ...Options>
class unordered_set;
template<class T, class ...Options>
class unordered_multiset;
Как уже упоминалось, для работы неупорядоченных контейнеров необходим вспомогательный массив. Навязчивые неупорядоченные контейнеры получают этот вспомогательный массив, упакованный в тип bucket_traits
(который также может быть настроен с помощью контейнера). Все неупорядоченные контейнеры получают в своих конструкторах объект bucket_traits
. Класс по умолчанию bucket_traits
инициализируется указателем на массив ведер и его размер:
#include <boost/intrusive/unordered_set.hpp>
using namespace boost::intrusive;
struct MyClass : public unordered_set_base_hook<>
{};
typedef unordered_set<MyClass>::bucket_type bucket_type;
typedef unordered_set<MyClass>::bucket_traits bucket_traits;
int main()
{
bucket_type buckets[100];
unordered_set<MyClass> uset(bucket_traits(buckets, 100));
return 0;
}
Каждому хешированному контейнеру нужны его собственные черты ведра, то есть его собственный вектор ведра. Два хешированных контейнера не могут иметь одинаковые элементы bucket_type
. Массив ковша должен быть уничтожен после контейнер с его использованием уничтожен, в противном случае результат не определен.
unordered_set
и unordered_multiset
получают те же опции, что и в разделе Как использовать Boost.Intrusive:
base_hook
member_hookHook,T Tvalue_traits: Чтобы указать тип крючка или свойства значения, используемые для настройки контейнера. (Чтобы узнать о ценностных характеристиках, перейдите в раздел Контейнеры с пользовательскими ValueTraits.)
constant_time_size<bool Enabled>
: Для активации операции постоянного времени size()
. По умолчанию: constant_time_size<true>
size_type<bool Enabled>
: Для указания типа, который будет использоваться для хранения размера контейнера. По умолчанию: size_type<std::size_t>
Также они могут получить дополнительные опции:
equal<class Equal>
: Функция равенства для объектов, подлежащих вставке в контейнеры. По умолчанию: equalstd::equal_to<>
hash<class Hash>
: Функция хеширования должна использоваться в контейнере. По умолчанию: hashboost::hash<T>
bucket_traits<class BucketTraits>
: Тип, который обертывает вектор ведра для использования неупорядоченным контейнером. По умолчанию: тип, инициализированный по адресу и размеру массива ковша, и сохраняет обе переменные внутри.
power_2_buckets<bool Enabled>
: Пользователь гарантирует, что будут использоваться только ковшовые массивы с мощностью двух длин. Затем контейнер будет использовать маски вместо операций модуля, чтобы получить номер ведра из значения хеширования. Маски быстрее, чем операции по модулю, и для некоторых приложений операции по модулю могут налагать значительные накладные расходы. В режиме отладки будет поднято утверждение, если пользователь предоставляет длину ведра, которая не равна мощности двух. По умолчанию: power_2_buckets<false>
.
cache_begin<bool Enabled>
: Примечание: этот вариант несовместим с auto_unlink
hooks. Благодаря своей внутренней структуре нахождение первого элемента неупорядоченного контейнера (начало()
операция) амортизируется в постоянное время. Можно ускорить начать()
и другие операции, связанные с ним (например, очистить()
), если контейнер кэширует внутренне положение первого элемента. Это накладывает накладные расходы одного указателя на размер контейнера. По умолчанию: cache_begin<false>
.
compare_hash<boolEnabled>
: Примечание: этот вариант требует store_hash<
> в крючке. Когда функция сравнения является дорогостоящей (например, строки с длинным общим предикатом) иногда (особенно когда коэффициент нагрузки высок или у нас есть много эквивалентных элементов в неупорядоченный_multiset
и нет оптимизировать_multikey<>
активируется в крючке) функция равенства является проблемой производительности. Два равных значения должны иметь одинаковые хэши, поэтому сравнение хеш-значений двух элементов перед использованием сравнительного функтора может ускорить некоторые реализации.
incremental<bool Enabled>
: Активирует инкрементное хеширование (также известное как линейное хеширование). Этот вариант подразумевает power_2_buckets<true>
и контейнеру потребуется мощность двух ведер. Для получения дополнительной информации о инкрементальном хэшировании см. Linear hash
в Википедии По умолчанию: incremental<false>
key_of_value<class KeyOfValueFunctionObject>
: Объект функции, который будет определять key_type
хранимого типа значения. Этот тип позволит использовать картографический интерфейс. См. Карта и многокарточный интерфейс с набором и мультисетом для деталей. По умолчанию: key_type
равно value_type
(сет-подобный интерфейс).
Теперь рассмотрим небольшой пример с использованием обоих крючков и обоих контейнеров:
#include <boost/intrusive/unordered_set.hpp>
#include <vector>
#include <functional>
#include <boost/functional/hash.hpp>
using namespace boost::intrusive;
class MyClass : public unordered_set_base_hook<>
{
int int_;
public:
unordered_set_member_hook<> member_hook_;
MyClass(int i)
: int_(i)
{}
friend bool operator== (const MyClass &a, const MyClass &b)
{ return a.int_ == b.int_; }
friend std::size_t hash_value(const MyClass &value)
{ return std::size_t(value.int_); }
};
typedef unordered_set<MyClass> BaseSet;
typedef member_hook<MyClass, unordered_set_member_hook<>, &MyClass::member_hook_>
MemberOption;
typedef unordered_multiset< MyClass, MemberOption> MemberMultiSet;
int main()
{
typedef std::vector<MyClass>::iterator VectIt;
std::vector<MyClass> values;
for(int i = 0; i < 100; ++i) values.push_back(MyClass(i));
std::vector<MyClass> values2(values);
BaseSet::bucket_type base_buckets[100];
MemberMultiSet::bucket_type member_buckets[200];
BaseSet base_set(BaseSet::bucket_traits(base_buckets, 100));
MemberMultiSet member_multi_set
(MemberMultiSet::bucket_traits(member_buckets, 200));
for(VectIt it(values.begin()), itend(values.end()); it != itend; ++it)
base_set.insert(*it);
for(VectIt it(values.begin()), itend(values.end()), it2(values2.begin())
; it != itend; ++it, ++it2){
member_multi_set.insert(*it);
member_multi_set.insert(*it2);
}
{
VectIt it(values.begin()), itend(values.end());
for(; it != itend; ++it){
if(base_set.count(*it) != 1) return 1;
if(member_multi_set.count(*it) != 2) return 1;
}
}
return 0;
}
Вместо использования класса bucket_traits
для хранения массива ковша пользователь может определить свой собственный класс для хранения массива ковша с помощью опции bucket_traits<>. Пользовательские характеристики ковша должны соответствовать следующему интерфейсу:
class my_bucket_traits
{
bucket_ptr bucket_begin();
const_bucket_ptr bucket_begin() const;
std::size_t bucket_count() const;
};
Следующие признаки ковша просто хранят указатель на массив ковша, но размер является постоянной времени компиляции. Обратите внимание на использование вспомогательных утилит unordered_bucket
и unordered_bucket_ptr
для получения типа ведра и его указателя перед определением неупорядоченного контейнера:
#include <boost/intrusive/unordered_set.hpp>
#include <boost/functional/hash.hpp>
#include <vector>
using namespace boost::intrusive;
class MyClass : public unordered_set_base_hook<>
{
int int_;
public:
MyClass(int i = 0) : int_(i)
{}
friend bool operator==(const MyClass &l, const MyClass &r)
{ return l.int_ == r.int_; }
friend std::size_t hash_value(const MyClass &v)
{ return boost::hash_value(v.int_); }
};
typedef base_hook< unordered_set_base_hook<> > BaseHookOption;
typedef unordered_bucket<BaseHookOption>::type BucketType;
typedef unordered_bucket_ptr<BaseHookOption>::type BucketPtr;
class custom_bucket_traits
{
public:
static const int NumBuckets = 100;
custom_bucket_traits(BucketPtr buckets)
: buckets_(buckets)
{}
BucketPtr bucket_begin() const { return buckets_; }
std::size_t bucket_count() const { return NumBuckets;}
private:
BucketPtr buckets_;
};
typedef unordered_set<MyClass, bucket_traits<custom_bucket_traits> > BucketTraitsUset;
int main()
{
typedef std::vector<MyClass>::iterator VectIt;
std::vector<MyClass> values;
for(int i = 0; i < 100; ++i) values.push_back(MyClass(i));
BucketType buckets[custom_bucket_traits::NumBuckets];
custom_bucket_traits btraits(buckets);
BucketTraitsUset uset(btraits);
for(VectIt it(values.begin()), itend(values.end()); it != itend; ++it)
uset.insert(*it);
return 0;
}