Как поясняется в разделеConcepts,Boost.Intrusiveконтейнерам нужен класс<ValueTraits>для выполнения преобразований между узлами и значениями пользователя.<ValueTraits>может быть явно сконфигурирован (используя опцию<value_traits<>>) или неявно сконфигурирован (используя крюки и их опции<base_hook<>>/<member_hook<>>).<ValueTraits>содержит всю информацию для склеивания<value_type>контейнеров и узла, который будет использоваться в алгоритмах узлов, поскольку эти типы могут быть разными. Кроме того,<ValueTraits>также хранит информацию о политике ссылок на значения, подлежащие вставке.
Вместо использованияBoost.Intrusiveпредопределенных крючков пользователь может захотеть разработать индивидуальные контейнеры, например, используя узлы, оптимизированные для конкретного приложения или совместимые с устаревшим ABI. Пользователь может захотеть иметь только два дополнительных указателя в своем классе и иногда вставлять класс в список, связанный вдвойне, и в список, связанный отдельно, в других ситуациях. Этого нельзя достичь с помощью.Навязчивыепредопределенные крючки. Теперь вместо<base_hook<...>>или<member_hook<...>>опций пользователь будет указывать<value_traits<...>>опции. Давайте посмотрим, как мы можем это сделать:
const_node_ptr: Для них это<node_traits::const_node_ptr>.
значение_тип: Тип, который пользователь хочет вставить в контейнер. Этот тип может быть таким же, как<node_traits::node>, но он может быть другим (например,<node_traits::node>может быть типом члена<value_type>). Если<value_type>и<node_traits::node>одинаковы, то<to_node_ptr>и<to_value_ptr>функции тривиальны.
указатель: Пояснительная записка<value_type>. Он должен быть такого же типа, как<node_ptr>: Если<node_ptr><node*>,<pointer>должно быть<value_type*>. Если<node_ptr><smart_ptr<node_traits::node>>,<pointer>должно быть<smart_ptr<value_type>>. Это может быть достигнуто с помощью<boost::intrusive::pointer_traits>(портативная реализация C++11<std::pointer_traits>).
const_pointer: Тип указателя на<constvalue_type>. Он должен быть такого же типа, как<node_ptr>: Если<node_ptr><node*>,<const_pointer>должен быть<constvalue_type*>. Если<node_ptr><smart_ptr<node_traits::node>>,<const_pointer>должно быть<smart_ptr<constvalue_type>>.
link_mode: Указывается, что<value_traits>требуется некоторая дополнительная работа или проверки из контейнера. Типы являются перечислениями, определенными в заголовке<link_mode.hpp>. Это возможные типы:
<normal_link>: Если эта политика связи указана в классе<ValueTraits>в качестве режима связи, контейнеры, сконфигурированные с таким<ValueTraits>, не будут устанавливать крючки стертых значений в состояние по умолчанию. Контейнеры также не будут проверять инициализацию крючков новых значений по умолчанию.
<safe_link>: Если эта политика связи указана как режим связи в классе<ValueTraits>, контейнеры, сконфигурированные с этим<ValueTraits>, установят крючки стертых значений в состояние по умолчанию. Контейнеры также проверят, что крючки новых значений инициализированы по умолчанию.
<auto_unlink>: Контейнеры с функциями постоянного времени не будут совместимы с<ValueTraits>, настроенными с этой политикой. Контейнеры также знают, что значение может быть незаметно удалено из контейнера без использования какой-либо функции, предоставляемой контейнерами.
статический узел_ptr to_node_ptr (value_type &value)истатический конст_node_ptr to_node_ptr (const value_type &value): Эти функции берут ссылку на тип значения и возвращают указатель на узел, который будет использоваться с алгоритмами узлов.
статический указатель на_value_ptr (node_ptr n)истатический const_pointer to_value_ptr (const_node_ptr n): Эти функции берут указатель на узел и возвращают указатель на значение, которое содержит узел.
Давайте определим наш собственный класс<value_traits>, чтобы иметь возможность использоватьBoost.Intrusiveконтейнеры со старой структурой C, определение которой не может быть изменено. Этот тип наследия имеет два указателя, которые можно использовать для создания списков, связанных по отдельности и вдвойне: в списках, связанных по отдельности, нам нужен только указатель, тогда как в списках, связанных вдвойне, нам нужны два указателя. Поскольку у нас есть только два указателя, мы не можем вставить объект одновременно в один и в два раза связанный список. Вот определение старого узла:
#include<boost/intrusive/link_mode.hpp>#include<boost/intrusive/list.hpp>#include<boost/intrusive/slist.hpp>#include<vector>//This node is the legacy type we can't modify and we want to insert in//intrusive list and slist containers using only two pointers, since//we know the object will never be at the same time in both lists.structlegacy_value{legacy_value*prev_;legacy_value*next_;intid_;};
Теперь мы должны определить класс NodeTraits, который будет реализовывать функции/типеды, которые сделают унаследованный узел совместимым с алгоритмамиBoost.Intrusive. После этого мы определим класс ValueTraits, который настроитBoost.Intrusiveконтейнеры:
//Define our own NodeTraits that will configure singly and doubly linked//list algorithms. Note that this node traits is compatible with//circular_slist_algorithms and circular_list_algorithms.namespacebi=boost::intrusive;structlegacy_node_traits{typedeflegacy_valuenode;typedeflegacy_value*node_ptr;typedefconstlegacy_value*const_node_ptr;staticnode*get_next(constnode*n){returnn->next_;}staticvoidset_next(node*n,node*next){n->next_=next;}staticnode*get_previous(constnode*n){returnn->prev_;}staticvoidset_previous(node*n,node*prev){n->prev_=prev;}};//This ValueTraits will configure list and slist. In this case,//legacy_node_traits::node is the same as the//legacy_value_traits::value_type so to_node_ptr/to_value_ptr//functions are trivial.structlegacy_value_traits{typedeflegacy_node_traitsnode_traits;typedefnode_traits::node_ptrnode_ptr;typedefnode_traits::const_node_ptrconst_node_ptr;typedeflegacy_valuevalue_type;typedeflegacy_value*pointer;typedefconstlegacy_value*const_pointer;staticconstbi::link_mode_typelink_mode=bi::normal_link;staticnode_ptrto_node_ptr(value_type&value){returnnode_ptr(&value);}staticconst_node_ptrto_node_ptr(constvalue_type&value){returnconst_node_ptr(&value);}staticpointerto_value_ptr(node_ptrn){returnpointer(n);}staticconst_pointerto_value_ptr(const_node_ptrn){returnconst_pointer(n);}};
Определение класса признаков ценности, который просто определяет<value_type>как<legacy_node_traits::node>, является распространенным подходом при определении настраиваемых навязчивых контейнеров, поэтомуBoost.Intrusiveпредлагает шаблонизированный<trivial_value_traits>класс, который делает именно то, что мы хотим:
Теперь мы можем просто определить контейнеры, которые будут хранить унаследованные объекты и написать небольшой тест:
//Now define an intrusive list and slist that will store legacy_value objectstypedefbi::value_traits<legacy_value_traits>ValueTraitsOption;typedefbi::value_traits<trivial_legacy_value_traits>TrivialValueTraitsOption;typedefbi::list<legacy_value,ValueTraitsOption>LegacyAbiList;typedefbi::slist<legacy_value,ValueTraitsOption>LegacyAbiSlist;typedefbi::list<legacy_value,TrivialValueTraitsOption>TrivialLegacyAbiList;typedefbi::slist<legacy_value,TrivialValueTraitsOption>TrivialLegacyAbiSlist;template<classList>booltest_list(){typedefstd::vector<legacy_value>Vect;//Create legacy_value objects, with a different internal numberVectlegacy_vector;for(inti=0;i<100;++i){legacy_valuevalue;value.id_=i;legacy_vector.push_back(value);}//Create the list with the objectsListmylist(legacy_vector.begin(),legacy_vector.end());//Now test both liststypenameList::const_iteratorbit(mylist.begin()),bitend(mylist.end());typenameVect::const_iteratorit(legacy_vector.begin()),itend(legacy_vector.end());//Test the objects inserted in our listfor(;it!=itend;++it,++bit)if(&*bit!=&*it)returnfalse;returntrue;}intmain(){returntest_list<LegacyAbiList>()&&test_list<LegacyAbiSlist>()&&test_list<TrivialLegacyAbiList>()&&test_list<TrivialLegacyAbiSlist>()?0:1;}
Как видно, несколько ключевых элементовBoost.Intrusiveмогут быть повторно использованы с пользовательскими типами пользователей, если пользователь не хочет использовать предоставленныесредства Boost.Intrusive.
В предыдущем примере<legacy_node_traits::node>тип и<legacy_value_traits::value_type>являются одним и тем же типом, но в этом нет необходимости. Можно иметь несколько<ValueTraits>, определяющих один и тот же<node_traits>тип (и, следовательно, тот же<node_traits::node>). Это уменьшает количество инстанциаций алгоритма узла, но теперь<ValueTraits::to_node_ptr>и<ValueTraits::to_value_ptr>функции должны предлагать преобразования между обоими типами. Рассмотрим небольшой пример:
Во-первых, мы определим узел, который будет использоваться в алгоритмах. Для связанного списка нам просто нужен узел, который хранит два указателя:
#include<boost/intrusive/link_mode.hpp>#include<boost/intrusive/list.hpp>#include<vector>//This is the node that will be used with algorithms.structsimple_node{simple_node*prev_;simple_node*next_;};
Теперь мы определим два разных типа, которые будут вставлены в навязчивые списки, и шаблон<ValueTraits>, который будет работать для обоих типов:
classbase_1{};classbase_2{};structvalue_1:publicbase_1,publicsimple_node{intid_;};structvalue_2:publicbase_1,publicbase_2,publicsimple_node{floatid_;};//Define the node traits. A single node_traits will be enough.structsimple_node_traits{typedefsimple_nodenode;typedefnode*node_ptr;typedefconstnode*const_node_ptr;staticnode*get_next(constnode*n){returnn->next_;}staticvoidset_next(node*n,node*next){n->next_=next;}staticnode*get_previous(constnode*n){returnn->prev_;}staticvoidset_previous(node*n,node*prev){n->prev_=prev;}};//A templatized value traits for value_1 and value_2template<classValueType>structsimple_value_traits{typedefsimple_node_traitsnode_traits;typedefnode_traits::node_ptrnode_ptr;typedefnode_traits::const_node_ptrconst_node_ptr;typedefValueTypevalue_type;typedefValueType*pointer;typedefconstValueType*const_pointer;staticconstboost::intrusive::link_mode_typelink_mode=boost::intrusive::normal_link;staticnode_ptrto_node_ptr(value_type&value){returnnode_ptr(&value);}staticconst_node_ptrto_node_ptr(constvalue_type&value){returnconst_node_ptr(&value);}staticpointerto_value_ptr(node_ptrn){returnstatic_cast<value_type*>(n);}staticconst_pointerto_value_ptr(const_node_ptrn){returnstatic_cast<constvalue_type*>(n);}};
Теперь определим два контейнера. Оба контейнера будут инстанцировать одни и те же алгоритмы списков<circular_list_algorithms<simple_node_traits>>, в связи с тем, что характеристики значений, используемые для определения контейнеров, обеспечивают один и тот же<node_traits>тип:
//Now define two intrusive lists. Both lists will use the same algorithms:// circular_list_algorithms<simple_node_traits>usingnamespaceboost::intrusive;typedeflist<value_1,value_traits<simple_value_traits<value_1>>>Value1List;typedeflist<value_2,value_traits<simple_value_traits<value_2>>>Value2List;
ВсеBoost.Intrusiveконтейнеры с использованием предопределенных крючков используют этот метод для минимизации размера кода: все возможные<list>контейнеры, созданные с предопределенными крючками, которые определяют один и тот же<VoidPointer>тип, используют одни и те же алгоритмы списка.
Предыдущий пример может быть дополнительно упрощен с использованием класса<derivation_value_traits>для определения класса признаков ценности со значением, которое хранит<simple_node>в качестве базового класса:
classbase_1{};classbase_2{};structvalue_1:publicbase_1,publicsimple_node{intid_;simple_nodenode_;};structvalue_2:publicbase_1,publicbase_2,publicsimple_node{simple_nodenode_;floatid_;};usingnamespaceboost::intrusive;//Now define the needed value traits using derivation_value_traitstypedefderivation_value_traits<value_1,simple_node_traits,normal_link>ValueTraits1;typedefderivation_value_traits<value_2,simple_node_traits,normal_link>ValueTraits2;//Now define two intrusive lists. Both lists will use the same algorithms:// circular_list_algorithms<simple_node_traits>typedeflist<value_1,value_traits<ValueTraits1>>Value1List;typedeflist<value_2,value_traits<ValueTraits2>>Value2List;
Мы можем даже выбрать для хранения<simple_node>в качестве члена<value_1>и<value_2>классов и использовать<member_value_traits>для определения необходимых классов значений:
classbase_1{};classbase_2{};structvalue_1:publicbase_1,publicsimple_node{intid_;simple_nodenode_;};structvalue_2:publicbase_1,publicbase_2,publicsimple_node{simple_nodenode_;floatid_;};usingnamespaceboost::intrusive;typedefmember_value_traits<value_1,simple_node_traits,&value_1::node_,normal_link>ValueTraits1;typedefmember_value_traits<value_2,simple_node_traits,&value_2::node_,normal_link>ValueTraits2;//Now define two intrusive lists. Both lists will use the same algorithms:// circular_list_algorithms<simple_node_traits>typedeflist<value_1,value_traits<ValueTraits1>>Value1List;typedeflist<value_2,value_traits<ValueTraits2>>Value2List;
До сих пор все показанные пользовательские значения не имеют состояния, то естьпреобразование между узлами и значениями реализуется в терминах статических функций. Можно использоватьхарактеристики значений, чтобы мы могли разделять узлы и значения, иизбегать модификации типов для вставки узлов.Boost.Навязчивыеразличают признаки значений значений с состоянием и без состояния, проверяя, являются ли все функции преобразования значений статическими или нет (за исключением Visual 7.1, поскольку обнаружение перегруженных статических функций невозможно, в этом случае реализация проверяет, пуст ли класс):
Если все функции преобразования значений Node<->являются статическими, то предполагается, чтоне имеет состояния. Преобразования должны быть статичными.
В противном случае предполагаетсясостояниехарактеристики значений.
Используя характеристики состояния, можно создавать контейнеры некопируемых/подвижных объектовбез измененияопределения класса, подлежащего вставке. Это интересное свойство достигается без использования глобальных переменных (символы без состояния могут использовать глобальные переменные для достижения одной и той же цели).
Гарантии безопасности ниток: Более качественные гарантии безопасности потоков могут быть достигнуты с помощью государственных характеристик, поскольку для доступа к глобальным ресурсам могут потребоваться примитивы синхронизации, которых можно избежать при использовании внутреннего состояния.
Гибкость: Состоятельный тип признаков ценности может быть настроен во время выполнения.
Полиморфизм времени выполнения: Ценностные признаки могут реализовывать узлы<->преобразования значений как виртуальные функции. Один тип контейнера может быть сконфигурирован во время выполнения для использования различных связей между узлом и значением.
У государственных черт ценности есть много преимуществ, но и некоторые недостатки:
Эффективность: Операции с ценностными признаками должны быть очень эффективными, поскольку они являются основными операциями, используемыми контейнерами.Тяжелый узел<->преобразование стоимости повредит производительности интрузивных контейнеров.
Гарантии исключения: Государственные ValueTraits должны поддерживать гарантии отсутствия броска, иначе инварианты контейнера не будут сохранены.
Статические функции: Некоторые статические функции, предлагаемые интрузивными контейнерами, недоступны, поскольку узлы<->преобразования значений не являются статическими.
Большие итераторы: Размер некоторых итераторов увеличивается, потому что итератор должен хранить указатель на статичные характеристики для реализации узла для преобразования значений (например,<operator*()>и<operator->()>).
Легкий и полезный пример статных характеристик стоимости - это когда массив значений может быть косвенно введен в список, гарантирующий отсутствие дополнительного распределения помимо первоначального резервирования ресурсов:
#include<boost/intrusive/list.hpp>usingnamespaceboost::intrusive;//This type is not modifiable so we can't store hooks or custom nodestypedefintidentifier_t;//This value traits will associate elements from an array of identifiers with//elements of an array of nodes. The element i of the value array will use the//node i of the node array:structstateful_value_traits{typedeflist_node_traits<void*>node_traits;typedefnode_traits::nodenode;typedefnode*node_ptr;typedefconstnode*const_node_ptr;typedefidentifier_tvalue_type;typedefidentifier_t*pointer;typedefconstidentifier_t*const_pointer;staticconstlink_mode_typelink_mode=normal_link;stateful_value_traits(pointerids,node_ptrnode_array):ids_(ids),nodes_(node_array){}///Note: non static functions!node_ptrto_node_ptr(value_type&value)const{returnthis->nodes_+(&value-this->ids_);}const_node_ptrto_node_ptr(constvalue_type&value)const{returnthis->nodes_+(&value-this->ids_);}pointerto_value_ptr(node_ptrn)const{returnthis->ids_+(n-this->nodes_);}const_pointerto_value_ptr(const_node_ptrn)const{returnthis->ids_+(n-this->nodes_);}private:pointerids_;node_ptrnodes_;};intmain(){constintNumElements=100;//This is an array of ids that we want to "store"identifier_tids[NumElements];//This is an array of nodes that is necessary to form the linked listlist_node_traits<void*>::nodenodes[NumElements];//Initialize id objects, each one with a different numberfor(inti=0;i!=NumElements;++i)ids[i]=i;//Define a list that will "link" identifiers using external nodestypedeflist<identifier_t,value_traits<stateful_value_traits>>List;//This list will store ids without modifying identifier_t instances//Stateful value traits must be explicitly passed in the constructor.Listmy_list(stateful_value_traits(ids,nodes));//Insert ids in reverse order in the listfor(identifier_t*it(&ids[0]),*itend(&ids[NumElements]);it!=itend;++it)my_list.push_front(*it);//Now test listsList::const_iteratorlist_it(my_list.cbegin());identifier_t*it_val(&ids[NumElements]),*it_rbeg_val(&ids[0]);//Test the objects inserted in the base hook listfor(;it_val!=it_rbeg_val;--it_val,++list_it)if(&*list_it!=&it_val[-1])return1;return0;}
Статья Containers with custom ValueTraits раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 17. Boost.Intrusive может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.