Wednesday, April 24, 2013

Unordered Set

The first container in Boost I want to take a look at is unordered_set [1]. The unordered_map is also very similar. In either case the goal is simple: put together an unordered_set that contains classes other than strings. 

Let's start by creating a main function (complete with includes for using unordered_set):

// boost dependencies
#include <boost/unordered_set.hpp>

// std dependencies
#include <string>

/**
 * A class that has a couple of members, just a simple number
 * and string. In a real world class you would probably put 
 * more "stuff" in here. 
 */
class Item
{
    public:
        Item(int importanceIn, std::string const& descriptionIn)
        : importance(importanceIn)
        , description(descriptionIn)
        {
        }

        int importance;
        std::string description;
};

std::ostream& operator<<(std::ostream& out, Item const& i)
{
    out << "ITEM(" << i.importance << "," << i.description << ");
    return out;
}

int main(int argc, char** argv)
{
    boost::unordered_set<Item> items;
    return 0;
}

Before you type all of this in, know that the example will NOT compile as it is! The reason it will not compile should be apparent in the definition of unordered set [1]:

namespace boost {
template <
class Key,
class Hash = boost::hash<Key>,
class Pred = std::equal_to<Key>,
class Alloc = std::allocator<Key> >
class unordered_set;

The second template argument to the unordered_set is assumed to be boost::hash<Item> in our case. Currently that specialization doesn't exist. Therefore we must provide a specialization to appease our compiler. To specialize, simply refer to the original template definition of boost::hash [2]. Our specialization is simply going to use the "importance" member variable as our hash value:

namespace boost
{

template <>
struct hash<Item>
{
    std::size_t operator()(Item const& item) const
    {
        // SHOULD USE boost::numeric_cast INSTEAD!
        return static_cast<size_t>(item.importance);
    }
};

}

This will please our compiler; but it's not the last of our problems! Currently we still do not have a specialization for std::equal_to [3]:

namespace std //< You aren't supposed to do this!
{

template <> 
struct equal_to<Item>
{
    bool operator()(Item const& lhs, Item const& rhs) const
    {
        return (lhs.importance == rhs.importance) && (lhs.description == rhs.description);
    }
};

}

Because we added an equal_to specialization we must also include <functional>. Our implementation now looks like this:

// boost dependencies
#include <boost/unordered_set.hpp>

// std dependencies
#include <functional>
#include <iostream>
#include <ostream>
#include <string>

/**
 * A class that has a couple of members, just a simple number
 * and string. In a real world class you would probably put 
 * more "stuff" in here. 
 */
class Item
{
    public:
        Item(int importanceIn, std::string const& descriptionIn)
        : importance(importanceIn)
        , description(descriptionIn)
        {
        }

        int importance;
        std::string description;
};

std::ostream& operator<<(std::ostream& out, Item const& i)
{
    out << "ITEM(" << i.importance << "," << i.description << ");
    return out;
}

//-----------------------------
// Our Specialization for Hash!
//-----------------------------
namespace boost
{

template <>
struct hash<Item>
{
    std::size_t operator()(Item const& item) const
    {
        // SHOULD USE boost::numeric_cast INSTEAD!
        return static_cast<size_t>(item.importance);
    }
};

}

//---------------------------------
// Our Specialization for equal_to!
//---------------------------------
namespace std //< You aren't supposed to do this!
{

template <> 
struct equal_to<Item>
{
    bool operator()(Item const& lhs, Item const& rhs) const
    {
        return (lhs.importance == rhs.importance) && (lhs.description == rhs.description);
    }
};

}


int main(int argc, char** argv)
{
    typedef boost::unordered_set<Item> ItemsContainer;
    typedef ItemsContainer::iterator Iterator;

    ItemsContainer items;

    items.insert(Item(5, "Fiver"));
    items.insert(Item(15, "Fifteen"));
    items.insert(Item(15, "Second Fifteen!"));        

    for(Iterator iter=items.begin(); iter != items.end(); ++iter)
    {
        std::cout << *iter << std::endl;
    }

    return 0;
}

I found the use of boost::unordered_set really simple and easy! I think what I like the best about this is that it is a header only class; this means we don't need to do any linking to use! All that the developer has to do is implement their own boost::hash and std::equal_to and they are good to go. I hope later classes are as easy to use. The full listing of unordered_set is at [4].

References 





No comments:

Post a Comment