How to use/create boost::multi_index?

后端 未结 1 1745
攒了一身酷
攒了一身酷 2020-12-16 04:03

Can someone explain to me in detail how to create a multi index map using boost::multi_index? I saw many examples online and also the boost page, but I could no

相关标签:
1条回答
  • 2020-12-16 04:24

    Boost.Multi-index offers an extremely customisable interface, at the cost of offering an extremely complex interface, so it's easy to understand why you're stuck.

    I'll present a commented example which should match your use case.

    First, our data:

    struct X
    {
      long long l; // assume unique
      int i1; // assume unique
      int i2; // assume non-unique
      // plus any ohter data you have in your class X
    };
    

    Next, we'll prepare one tag for each index which we want the container to have. Tags aren't strictly necessary (indexes can be accessed by their order), but it's more convenient to provide a name for each index:

    struct IndexByL {};
    struct IndexByI1 {};
    struct IndexByI2 {};
    

    Now, we have what we need to put together the type of the container:

    using Container = boost::multi_index_container<
      X*, // the data type stored
      boost::multi_index::indexed_by< // list of indexes
        boost::multi_index::hashed_unique<  //hashed index over 'l'
          boost::multi_index::tag<IndexByL>, // give that index a name
          boost::multi_index::member<X, long long, &X::l> // what will be the index's key
        >,
        boost::multi_index::ordered_unique<  //ordered index over 'i1'
          boost::multi_index::tag<IndexByI1>, // give that index a name
          boost::multi_index::member<X, int, &X::i1> // what will be the index's key
        >,
        boost::multi_index::hashed_non_unique<  //hashed non-unique index over 'i2'
          boost::multi_index::tag<IndexByI2>, // give that index a name
          boost::multi_index::member<X, int, &X::i2> // what will be the index's key
        >
      >
    >;
    

    That's it, we have a container. Next, here's how we can use it:

    Container c;  // empty container
    X x1{...}, x2{...}, x3{...};  // some data
    
    // Insert some elements
    auto& indexByL = c.get<IndexByL>(); // here's where index tags (=names) come in handy
    indexByL.insert(&x1);
    indexByL.insert(&x2);
    indexByL.insert(&x3);
    
    // Look up by i1
    auto& indexByI1 = c.get<IndexByI1>();
    auto itFound = indexByI1.find(42);
    if (itFound != indexByI1.end())
    {
      X *x = *itFound;
    }
    
    // Look up by i2
    auto& indexByI2 = c.get<IndexByI2>();
    size_t numberOfHundreds = indexByI2.count(100);
    

    [Live example]

    And now, some prose about how the beast works in general.

    You define a multi-index container by specifying the type of objects it will store (X* in your case), and one or more indexes which can be used to access the stored objects. Think of indexes as interfaces for accessing the data.

    Indexes can be of different kinds:

    • Indexes based on ordering by a key (think std::set or std::map)
    • Indexes based on ranking by a key (think the same plus easy access to nth element)
    • Indexes based on hashing a key (think std::unordered_set or std::unordered_map)
    • Indexes based on accessing in stable order (think std::list)
    • Indexes based on random access in stable order (think std::vector)

    The key-based indexes can also be either unique (like std::map), or non-unique (like std::multimap).

    When defining the container, you pass each index you want to have as one template argument to boost::multi_index::indexed_by. (In our example above, I added three indexes).

    For indexes which do not use a key (stable order & random access), nothing needs to be specified; you just say "I want such an index."

    For key-based indexes, you also need to specify how the key is obtained from the data. That's where key extractors come into play. (These are the three uses of boost::multi_index::member in the example). Basically, for each index, you provide a recipe (or algorithm) for deriving the key from the data stored in the container. Currently available key extractors are:

    • Use the element itself: identity
    • Use a data member of the element: member
    • Use a (constant) member function of the element: [const_]mem_fun
    • Use a global function: global_fun
    • Combine multiple key extractors into one: composite_key

    Note that key extractors are able to dereference pointers transparently. That is, if your data element is a pointer to class C, you can specify key extractors over class C and the dereference will happen automagically. (This property is also used in the example).

    This way a container with indexes is defined. To access an index, you call the get member function template on the container. You can refer to an index by its sequential number in the list of template arguments of indexed_by. For more readable manipulation, you can however introduce a tag for each index (or just some of them). A tag is an arbitrary type (usually an empty structure with a suitable name) which allows you to use that type as template argument for get instead of the index's sequential number. (This is also used in the example).

    Once you retrieve a reference to an index from the container, you can use it just like the data structure to which the index corresponds (map, hashset, vector, ...). Changes done through that index will affect the entire container.

    0 讨论(0)
提交回复
热议问题