C++ Boost.Multiindex - Intro

in #cpp3 years ago

Boost.Multiindex - Intro

Boot Multiindex is a container that gives you multiple interfaces, called indexes, to lookup the elements within the container.

Ex: If you have a bunch of elements that you want to access in multiple ways, say sometimes you want to access the elements in a default sorted manner (like a set) but other times you want to access them using your own controlled the sequence (like a list). If you use a boost.multiindex container you can fill it with all the elements you want and then when it comes time to access the elements you can pick the interface (in this example either a set or a list) you want to use. This allows you to only have a single container with multiple interfaces which can be used appropriately to better suit different scenarios in your application.

You state all the interface types you will be using when you define your multiindex container.

Ex:
Say you had a car with the following properties.
  • name
  • hp // horse power
  • and the methods
  • isFast() // returns true or false

  • You could model it like so:

    class car {
    public:
        std::string name;
    
        car(const std::string& name, int hp, bool is_fast)
            : name(name), hp_(hp), is_fast_(is_fast) {}
    
        bool isFast() const { return is_fast_; }
        friend int getHp(const car& c) { return c.hp_; }
    private:
        int         hp_;
        bool        is_fast_;
    }
    

    Defining your multiindex container
    typdef multi_index_container<
        car,         // element data structure
        indexed_by<  // Here is put the interfaces you will use with the container
            // interface 0
            hashed_unique<  // - all names must be unique and lookup by has value
                // look up by member variable
                // < data_struct,  type,  member variable >
                member<car, std::string, &car::name> >,  // looked up by name
            // interface 1
            hashed_non_unique<  // - not unique and look up by member function
                // look up by constant member function
                //  < data_struct, type, member function >
                const_mem_fun<car, bool, &car::isFast> >,  // looked up by isFast()
            // interface 2
            ordered_non_unique< // - orderd not unique look up by global function
                // look up by global function
                //        param_type, return type, function name
                global_fun<const car&, int, getHp> >    // looked up by global function getHp()
        >
    > cars_multi_idx_type;  // new container type
    

    Each car we put in this container now must have a unique name.


    cars_multi_idx_type cars;
    
    cars.insert(car("mustang", 400, true));
    cars.insert(car("prius",   180, false));
    cars.insert(car("370z",    350, true));
    cars.insert(car("camaro",  380, true));
    

    member function find: when you don't specify an interface, the first one is used by default.


    auto itr = cars.find("mustang");
    if (itr != cars.end())
        std::cout << itr->isFast() << std::endl;
    

    First thing you do is get the interface, unless you are using the 0th interface by default to access an index (interface). Now we are using the get() method to use the 'ordered_non_unique<>' interface.

    auto& hp_index = cars.get<2>();
    

    Now you can look up cars as if they were contained in an ordered_non_unique container with a the global function getHp() used to return an iterator to the cars hp_.


    auto begin = hp_index.lower_bound(180);
    auto end   = hp_index.upper_bound(380);
    
    /* For each hp_index from begin to end get the rest
    *  of the car object */
    std::for_each(begin, end, [](const car& c) {
        std::cout << c.name << std::endl;
        if (c.isFast())
            std::count << " is fast!\n";
    }); 
    

    You can also make other iterators to different indexes (interfaces) by projecting them. A name iterator using interface 0 is created with our existing 'hp_index' iterator 'begin'.


    auto name_itr    = cars.project<0>(begin);
    auto is_fast_itr = cars.project<1>(begin);
    
    /* Get an iterator from an element */
    auto itr = cars.find("mustang");
    const car& c = *itr;
    itr = cars.iterator_to(c);
    

    You CAN NOT change an element with an iterator. This is beacuse the iterator is only used for the specific interface. If you want to modify an element you will need to update it for use with every interface. To update an element you use the member function modify and pass the iterator and a lambda function. The lambda function will get a reference to your object, car, at the position of the itr. Now you can change the car object.


    cars.modify(itr, [](car& c) {
        c.name = "shelby cobra";  // changes 'mustang' to 'shelby cobra'
    });
    
    /* modify by key */
    cars.modify_key(itr, [](std::string& n) {
        n = "shelby cobra";
    });
    
    /* Replace elements */
    cars.replace(itr, car("civic", 200, false));