unordered_set storing elements as pointers

2019-06-28 05:03发布

问题:

To narrow it down: I'm currently using Boost.Unordered. I see two possible solutions:

  1. Define my own Equality Predicates and Hash Functions and to utilize templates (maybe is_pointer) to distinct between pointers and instances;

  2. Simply to extend boost::hash by providing hash_value(Type* const& x) as for hashing; and add == operator overload as free function with (Type* const& x, Type* const& y) parameters as for equality checking.

I'm not sure whether both variations are actually possible, since I didn't test them. I would like to find out you handle this problem. Implementations are welcome :)

EDIT 1: What about this?

template<class T>
struct Equals: std::binary_function<T, T, bool> {
    bool operator()(T const& left, T const& right) const {
        return left == right;
    }
};

template<class T>
struct Equals<T*> : std::binary_function<T*, T*, bool> {
    bool operator()(T* const& left, T* const& right) const {
        return *left == *right;
    }
};

EDIT 2:

I've just defined:

friend std::size_t hash_value(Base const& base) {
    boost::hash<std::string> hash;

    return hash(base.string_);
}

friend std::size_t hash_value(Base* const& base) {
    return hash_value(*base);
}

And then:

Derived d1("x");
Derived d2("x");

unordered_set<Base*> set;

set.insert(&d1);

assert(set.find(&d2) == end());

Debugger says that friend std::size_t hash_value(Base* const& base) is never called (GCC 4.7). Why is that?

EDIT 3: I found out that template <class T> std::size_t hash_value(T* const& v) in boost/functional/hash.hpp on line #215 (Boost 1.49) is Boost's specialization for pointers and it simply masks your custom implementation of hash_value such as mine in EDIT 2. Therefore, it seems like the only way here is to create a custom Hash Functor.

回答1:

For the hash function, you have a choice between specializing boost::hash (or std::hash in the newer standard) or defining a new functor class. These alternatives work equally well.

For the equality operator, you need to define a new functor, because you cannot redefine the equality operator over pointers. It's a built-in operator (defined in functional terms as bool operator==( T const *x, T const *y )) and cannot be replaced.

Both of these can be defined generically by using a templated operator() in a non-templated class.

struct indirect_equal {
    template< typename X, typename Y >
    bool operator() ( X const &lhs, Y const &rhs )
        { return * lhs == * rhs; }
};

Follow a similar pattern for the hasher.



回答2:

Taking into consideration all edits in the original post I would like to provide complete solution which satisfies my needs:

1. Equality:

template<class T>
struct Equal: ::std::binary_function<T, T, bool> {
    bool operator()(T const& left, T const& right) const {
        ::std::equal_to<T> equal;

        return equal(left, right);
    }
};

template<class T>
struct Equal<T*> : ::std::binary_function<T*, T*, bool> {
    bool operator()(T* const & left, T* const & right) const {
        Equal<T> equal;

        return equal(*left, *right);
    }
};

2. Hashing:

template<class T>
struct Hash: ::std::unary_function<T, ::std::size_t> {
    ::std::size_t operator()(T const & value) const {
        ::boost::hash<T> hash;

        return hash(value);
    }
};

template<class T>
struct Hash<T*> : ::std::unary_function<T*, ::std::size_t> {
    ::std::size_t operator()(T* const & value) const {
        Hash<T> hash;

        return hash(*value);
    }
};

So now I can continue using Boost's hash_value and it will not get masked for pointer types by Boost's default implementation (see EDIT 3).

3. Example:

In my application I have a thin wrapper for unordered_set which now looks like that:

template<class T, class H = Hash<T>, class E = Equal<T> >
class Set {
public:

// code omitted...

    bool contains(const T& element) const {
        return s_.find(element) != end();
    }

    bool insert(const T& element) {
        return s_.insert(element).second;
    }

// code omitted...

private:
    ::boost::unordered::unordered_set<T, H, E> s_;
};

So if we have some base class:

class Base {
public:
    Base(const ::std::string& string) {
        if (string.empty())
            throw ::std::invalid_argument("String is empty.");

        string_ = string;
    }

    virtual ~Base() {
    }

    friend bool operator==(const Base& right, const Base& left) {
        return typeid(right) == typeid(left) && right.string_ == left.string_;
    }

    friend bool operator!=(const Base& right, const Base& left) {
        return !(right == left);
    }

    friend ::std::size_t hash_value(Base const& base) {
        ::boost::hash<std::string> hash;

        return hash(base.string_);
    }

    friend ::std::size_t hash_value(Base* const& base) {
        return hash_value(*base);
    }

private:
    ::std::string string_;
};

And some derived class:

class Derived: public Base {
public:
    Derived(const ::std::string& string) :
            Base(string) {
    }

    virtual ~Derived() {
    }
};

Then we can even use polymorphism (which was my primary intention BTW):

Derived d1("¯\_(ツ)_/¯");
Derived d2("¯\_(ツ)_/¯");

Set<Base*> set;

set.insert(&d1);

assert(set.contains(&d2));

Hope this helps. Any suggestions are welcome.