Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Containers with custom ValueTraits

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 17. Boost.Intrusive

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Как поясняется в разделеConcepts,Boost.Intrusiveконтейнерам нужен класс<ValueTraits>для выполнения преобразований между узлами и значениями пользователя.<ValueTraits>может быть явно сконфигурирован (используя опцию<value_traits<>>) или неявно сконфигурирован (используя крюки и их опции<base_hook<>>/<member_hook<>>).<ValueTraits>содержит всю информацию для склеивания<value_type>контейнеров и узла, который будет использоваться в алгоритмах узлов, поскольку эти типы могут быть разными. Кроме того,<ValueTraits>также хранит информацию о политике ссылок на значения, подлежащие вставке.

Вместо использованияBoost.Intrusiveпредопределенных крючков пользователь может захотеть разработать индивидуальные контейнеры, например, используя узлы, оптимизированные для конкретного приложения или совместимые с устаревшим ABI. Пользователь может захотеть иметь только два дополнительных указателя в своем классе и иногда вставлять класс в список, связанный вдвойне, и в список, связанный отдельно, в других ситуациях. Этого нельзя достичь с помощью.Навязчивыепредопределенные крючки. Теперь вместо<base_hook<...>>или<member_hook<...>>опций пользователь будет указывать<value_traits<...>>опции. Давайте посмотрим, как мы можем это сделать:

<ValueTraits>имеет следующий интерфейс:

#include <boost/intrusive/pointer_traits.hpp>
#include <boost/intrusive/link_mode.hpp>
struct my_value_traits
{
   typedef implementation_defined                                    node_traits;
   typedef implementation_defined                                    value_type;
   typedef node_traits::node_ptr                                     node_ptr;
   typedef node_traits::const_node_ptr                               const_node_ptr;
   typedef boost::intrusive::pointer_traits<node_ptr>::rebind_traits
      <value_type>::type::pointer                                    pointer;
   typedef boost::intrusive::pointer_traits<node_ptr>::rebind_traits
      <const value_type>::type::pointer                              const_pointer;
   static const link_mode_type link_mode = some_linking_policy;
   static node_ptr       to_node_ptr    (value_type &value);
   static const_node_ptr to_node_ptr    (const value_type &value);
   static pointer        to_value_ptr   (node_ptr n);
   static const_pointer  to_value_ptr   (const_node_ptr n);
};

Рассмотрим каждый тип и функцию:

  • узловые черты: Конфигурация узла, необходимая алгоритмам узлов. Эти особенности узлов и алгоритмы описаны в предыдущей главе:Алгоритмы узлов.
    • Если мои_значения_черты предназначены для использования с<slist>,<node_traits>следует использовать интерфейс, необходимый<circular_slist_algorithms>.
    • Если мой_value_traits предназначен для использования с<list>,<node_traits>должен следовать интерфейсу, необходимому<circular_list_algorithms>.
    • Если мой_value_traits предназначен для использования с<set>/<multiset>,<node_traits>должен следовать интерфейсу, необходимому<rbtree_algorithms>.
    • Если мой_value_traits предназначен для использования с<unordered_set>/<unordered_multiset>,<node_traits>должен следовать интерфейсу, необходимому<circular_slist_algorithms>.
  • node_ptr: Для справки<node_traits::node_ptr>.
  • 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: Тип указателя на<const value_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.
struct legacy_value
{
   legacy_value *prev_;
   legacy_value *next_;
   int id_;
};

Теперь мы должны определить класс 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.
namespace bi = boost::intrusive;
struct legacy_node_traits
{
   typedef legacy_value                            node;
   typedef legacy_value *                          node_ptr;
   typedef const legacy_value *                    const_node_ptr;
   static node *get_next(const node *n)            {  return n->next_;  }
   static void set_next(node *n, node *next)       {  n->next_ = next;  }
   static node *get_previous(const node *n)        {  return n->prev_;  }
   static void set_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.
struct legacy_value_traits
{
   typedef legacy_node_traits                                  node_traits;
   typedef node_traits::node_ptr                               node_ptr;
   typedef node_traits::const_node_ptr                         const_node_ptr;
   typedef legacy_value                                        value_type;
   typedef legacy_value *                                      pointer;
   typedef const legacy_value *                                const_pointer;
   static const bi::link_mode_type link_mode = bi::normal_link;
   static node_ptr to_node_ptr (value_type &value)             {  return node_ptr(&value); }
   static const_node_ptr to_node_ptr (const value_type &value) {  return const_node_ptr(&value); }
   static pointer to_value_ptr(node_ptr n)                     {  return pointer(n); }
   static const_pointer to_value_ptr(const_node_ptr n)         {  return const_pointer(n); }
};

Определение класса признаков ценности, который просто определяет<value_type>как<legacy_node_traits::node>, является распространенным подходом при определении настраиваемых навязчивых контейнеров, поэтомуBoost.Intrusiveпредлагает шаблонизированный<trivial_value_traits>класс, который делает именно то, что мы хотим:

typedef bi::trivial_value_traits<legacy_node_traits, bi::normal_link> trivial_legacy_value_traits;

Теперь мы можем просто определить контейнеры, которые будут хранить унаследованные объекты и написать небольшой тест:

//Now define an intrusive list and slist that will store legacy_value objects
typedef bi::value_traits<legacy_value_traits>         ValueTraitsOption;
typedef bi::value_traits<trivial_legacy_value_traits> TrivialValueTraitsOption;
typedef bi::list<legacy_value,  ValueTraitsOption>          LegacyAbiList;
typedef bi::slist<legacy_value, ValueTraitsOption>          LegacyAbiSlist;
typedef bi::list<legacy_value,  TrivialValueTraitsOption>   TrivialLegacyAbiList;
typedef bi::slist<legacy_value, TrivialValueTraitsOption>   TrivialLegacyAbiSlist;
template<class List>
bool test_list()
{
   typedef std::vector<legacy_value> Vect;
   //Create legacy_value objects, with a different internal number
   Vect legacy_vector;
   for(int i = 0; i < 100; ++i){
      legacy_value value;     value.id_ = i;    legacy_vector.push_back(value);
   }
   //Create the list with the objects
   List mylist(legacy_vector.begin(), legacy_vector.end());
   //Now test both lists
   typename List::const_iterator bit(mylist.begin()), bitend(mylist.end());
   typename Vect::const_iterator it(legacy_vector.begin()), itend(legacy_vector.end());
   //Test the objects inserted in our list
   for(; it != itend; ++it, ++bit)
      if(&*bit != &*it) return false;
   return true;
}
int main()
{
   return test_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.
struct simple_node
{
   simple_node *prev_;
   simple_node *next_;
};

Теперь мы определим два разных типа, которые будут вставлены в навязчивые списки, и шаблон<ValueTraits>, который будет работать для обоих типов:

class base_1{};
class base_2{};
struct value_1 :  public base_1, public simple_node
{  int   id_;  };
struct value_2 :  public base_1, public base_2, public simple_node
{  float id_;  };
//Define the node traits. A single node_traits will be enough.
struct simple_node_traits
{
   typedef simple_node                             node;
   typedef node *                                  node_ptr;
   typedef const node *                            const_node_ptr;
   static node *get_next(const node *n)            {  return n->next_;  }
   static void set_next(node *n, node *next)       {  n->next_ = next;  }
   static node *get_previous(const node *n)        {  return n->prev_;  }
   static void set_previous(node *n, node *prev)   {  n->prev_ = prev;  }
};
//A templatized value traits for value_1 and value_2
template<class ValueType>
struct simple_value_traits
{
   typedef simple_node_traits                                  node_traits;
   typedef node_traits::node_ptr                               node_ptr;
   typedef node_traits::const_node_ptr                         const_node_ptr;
   typedef ValueType                                           value_type;
   typedef ValueType *                                         pointer;
   typedef const ValueType *                                   const_pointer;
   static const boost::intrusive::link_mode_type link_mode = boost::intrusive::normal_link;
   static node_ptr to_node_ptr (value_type &value)             {  return node_ptr(&value); }
   static const_node_ptr to_node_ptr (const value_type &value) {  return const_node_ptr(&value); }
   static pointer to_value_ptr(node_ptr n)                     {  return static_cast<value_type*>(n); }
   static const_pointer to_value_ptr(const_node_ptr n)         {  return static_cast<const value_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>
using namespace boost::intrusive;
typedef list <value_1, value_traits<simple_value_traits<value_1> > > Value1List;
typedef list <value_2, value_traits<simple_value_traits<value_2> > > Value2List;

ВсеBoost.Intrusiveконтейнеры с использованием предопределенных крючков используют этот метод для минимизации размера кода: все возможные<list>контейнеры, созданные с предопределенными крючками, которые определяют один и тот же<VoidPointer>тип, используют одни и те же алгоритмы списка.

Предыдущий пример может быть дополнительно упрощен с использованием класса<derivation_value_traits>для определения класса признаков ценности со значением, которое хранит<simple_node>в качестве базового класса:

class base_1{};
class base_2{};
struct value_1 :  public base_1, public simple_node
{
   int   id_;
   simple_node node_;
};
struct value_2 :  public base_1, public base_2, public simple_node
{
   simple_node node_;
   float id_;
};
using namespace boost::intrusive;
//Now define the needed value traits using derivation_value_traits
typedef derivation_value_traits<value_1, simple_node_traits, normal_link> ValueTraits1;
typedef derivation_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>
typedef list <value_1, value_traits<ValueTraits1> > Value1List;
typedef list <value_2, value_traits<ValueTraits2> > Value2List;

Мы можем даже выбрать для хранения<simple_node>в качестве члена<value_1>и<value_2>классов и использовать<member_value_traits>для определения необходимых классов значений:

class base_1{};
class base_2{};
struct value_1 :  public base_1, public simple_node
{
   int   id_;
   simple_node node_;
};
struct value_2 :  public base_1, public base_2, public simple_node
{
   simple_node node_;
   float id_;
};
using namespace boost::intrusive;
typedef member_value_traits
   <value_1, simple_node_traits, &value_1::node_, normal_link> ValueTraits1;
typedef member_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>
typedef list <value_1, value_traits<ValueTraits1> > Value1List;
typedef list <value_2, value_traits<ValueTraits2> > Value2List;

До сих пор все показанные пользовательские значения не имеют состояния, то естьпреобразование между узлами и значениями реализуется в терминах статических функций. Можно использоватьхарактеристики значений, чтобы мы могли разделять узлы и значения, иизбегать модификации типов для вставки узлов.Boost.Навязчивыеразличают признаки значений значений с состоянием и без состояния, проверяя, являются ли все функции преобразования значений статическими или нет (за исключением Visual 7.1, поскольку обнаружение перегруженных статических функций невозможно, в этом случае реализация проверяет, пуст ли класс):

  • Если все функции преобразования значений Node<->являются статическими, то предполагается, чтоне имеет состояния. Преобразования должны быть статичными.
  • В противном случае предполагаетсясостояниехарактеристики значений.

Используя характеристики состояния, можно создавать контейнеры некопируемых/подвижных объектовбез измененияопределения класса, подлежащего вставке. Это интересное свойство достигается без использования глобальных переменных (символы без состояния могут использовать глобальные переменные для достижения одной и той же цели).

  • Гарантии безопасности ниток: Более качественные гарантии безопасности потоков могут быть достигнуты с помощью государственных характеристик, поскольку для доступа к глобальным ресурсам могут потребоваться примитивы синхронизации, которых можно избежать при использовании внутреннего состояния.
  • Гибкость: Состоятельный тип признаков ценности может быть настроен во время выполнения.
  • Полиморфизм времени выполнения: Ценностные признаки могут реализовывать узлы<->преобразования значений как виртуальные функции. Один тип контейнера может быть сконфигурирован во время выполнения для использования различных связей между узлом и значением.

У государственных черт ценности есть много преимуществ, но и некоторые недостатки:

  • Эффективность: Операции с ценностными признаками должны быть очень эффективными, поскольку они являются основными операциями, используемыми контейнерами.Тяжелый узел<->преобразование стоимости повредит производительности интрузивных контейнеров.
  • Гарантии исключения: Государственные ValueTraits должны поддерживать гарантии отсутствия броска, иначе инварианты контейнера не будут сохранены.
  • Статические функции: Некоторые статические функции, предлагаемые интрузивными контейнерами, недоступны, поскольку узлы<->преобразования значений не являются статическими.
  • Большие итераторы: Размер некоторых итераторов увеличивается, потому что итератор должен хранить указатель на статичные характеристики для реализации узла для преобразования значений (например,<operator*()>и<operator->()>).

Легкий и полезный пример статных характеристик стоимости - это когда массив значений может быть косвенно введен в список, гарантирующий отсутствие дополнительного распределения помимо первоначального резервирования ресурсов:

#include <boost/intrusive/list.hpp>
using namespace boost::intrusive;
//This type is not modifiable so we can't store hooks or custom nodes
typedef int identifier_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:
struct stateful_value_traits
{
   typedef list_node_traits<void*>           node_traits;
   typedef node_traits::node                 node;
   typedef node *                            node_ptr;
   typedef const node *                      const_node_ptr;
   typedef identifier_t                      value_type;
   typedef identifier_t *                    pointer;
   typedef const identifier_t *              const_pointer;
   static const link_mode_type link_mode =   normal_link;
   stateful_value_traits(pointer ids, node_ptr node_array)
      :  ids_(ids),  nodes_(node_array)
   {}
   ///Note: non static functions!
   node_ptr to_node_ptr (value_type &value) const
      {  return this->nodes_ + (&value - this->ids_); }
   const_node_ptr to_node_ptr (const value_type &value) const
      {  return this->nodes_ + (&value - this->ids_); }
   pointer to_value_ptr(node_ptr n) const
      {  return this->ids_ + (n - this->nodes_); }
   const_pointer to_value_ptr(const_node_ptr n) const
      {  return this->ids_ + (n - this->nodes_); }
   private:
   pointer  ids_;
   node_ptr nodes_;
};
int main()
{
   const int NumElements = 100;
   //This is an array of ids that we want to "store"
   identifier_t                  ids   [NumElements];
   //This is an array of nodes that is necessary to form the linked list
   list_node_traits<void*>::node nodes [NumElements];
   //Initialize id objects, each one with a different number
   for(int i = 0; i != NumElements; ++i)   ids[i] = i;
   //Define a list that will "link" identifiers using external nodes
   typedef list<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.
   List  my_list (stateful_value_traits (ids, nodes));
   //Insert ids in reverse order in the list
   for(identifier_t * it(&ids[0]), *itend(&ids[NumElements]); it != itend; ++it)
      my_list.push_front(*it);
   //Now test lists
   List::const_iterator   list_it (my_list.cbegin());
   identifier_t *it_val(&ids[NumElements]), *it_rbeg_val(&ids[0]);
   //Test the objects inserted in the base hook list
   for(; it_val != it_rbeg_val; --it_val, ++list_it)
      if(&*list_it  != &it_val[-1])   return 1;
   return 0;
}

PrevUpHomeNext

Статья Containers with custom ValueTraits раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 17. Boost.Intrusive может быть полезна для разработчиков на c++ и boost.




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.



:: Главная :: Chapter 17. Boost.Intrusive ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 17:15:27/0.034596920013428/1