Search code examples
c++shared-ptrrelational-operators

C++ using shared_ptr but having my object's relational operators called?


I'm playing around writing my own heap class. My templated heap class requires the operators '>' and '<' to be defined on the template type.

All seemed to work fine when using an instance of a sample class I wrote (and also worked fine on int). However, since there is so much construction of instances as class instances move from different nodes in the heap, I decided to see what happened when I created a heap of a shared_ptr of my class. While I did see the number of instances constructed go way down, the heap was not working correctly as it appears the smart pointer '>' and '<' are getting called which I guess just compares smart pointer references.

One solution that comes to mind is allowing for a comparison type, just as many of the stl types do, so that I can pass in my own comparison type into the heap class which will dereference the shared_ptr and call the operation on the underlying type.

Some docs I read on the shared_ptr said that they implement the relational operator (namely <) so that they could be used as keys in associative containers. I'm trying to think about when I might want to use the shared_ptr as the key instead of having a specific key of my own.

heap of my sample type which appears to work fine:

heap<foo> foo_heap(heap_type::max);
for (unsigned int i = 0; i < 10; ++i)
    {
    std::string s = "string ";
    s += ('0' + i);
    foo f(i, s);
    foo_heap.push(f);
    }
cout << "root: " << foo_heap.top() << endl;

wrapping my sample class in a shared_ptr which doesn't work, eg. heap constraint not met in terms of what I'm trying to accomplish.

heap<shared_ptr<foo>> foo_heap_smart(heap_type::max);
for (unsigned int i = 0; i < 10; ++i)
    {
    std::string s = "string ";
    s += ('0' + i);
    shared_ptr<foo> f(new foo(i, s));
    foo_heap_smart.push(f);
    }
cout << "root: " << *(foo_heap_smart.top()) << endl;

my sample foo class:

class foo
{
public:
    foo(int value, std::string s) : _value(value), _s(s)
    {
        std::cout << "foo::foo()" << std::endl;
    }

    foo(const foo& f) : _value(f._value), _s(f._s)
    {
        std::cout << "foo::foo(const foo& f)" << std::endl;
    }

    ~foo()
    {
        std::cout << "foo::~foo()" << std::endl;
    }

    virtual void operator=(const foo& f)
    {
        std::cout << "foo::operator=()" << std::endl;
        this->_value = f._value;
        this->_s = f._s;
    }

    virtual bool operator<(const foo& right)
    {
        return this->_value < right._value;
    }

    virtual bool operator>(const foo& right)
    {
        return this->_value > right._value;
    }

    void print(ostream& stm) const
    {
        stm << "value: " << this->_value << ", s: " << this->_s;
    }

private:
    int _value;
    std::string _s;
};

So I assume that many have run into a similar problem. Just wondering what the prescribed solution is. As I mentioned, I think I know of what appears might be a good solution, but wanted to check as it seems that the smart pointer could cause many problems because of their implementation of the relational operators.

Thanks, Nick


Solution

  • The prescribed solution is to provide your own version of the comparison operator if the default one does not suit your need. A better design for your heap class would be to take a Comparator type as well, which can default to std::less.

    template <typename T, typename Comp = std::less<T>>
    class heap {
    ...
    };
    

    And now provide you own version of less specialized for shared_ptr.

    template <typename T>
    struct less<shared_ptr<T>> {
        bool operator()(const shared_ptr<T>& a, const shared_ptr<T>& b) const {
          *a < *b;
        }
    };
    

    For a better design, you cann add some metaprogramming hack to make it work only for type T which can be compared.