3

Is there a possibility to compare two maps containing std::unique_ptrs?

In my specific case, I want to compare two maps containing tuples of std::unique_ptr (The std::tuple part is not relevant to the question, but I think it is important to consider the complexity induced by using heterogeneous types when manually implementing the most basic operations, like comparison).

class MyObject
{
public:
    bool operator==(const MyObject& other) const
    {
        return member1 == other.member1;
    }

    bool operator!=(const MyObject& other) const;

private:
    std::string member1 = "specific string";
};

TEST(General, CompareMapOfTuplesOfUniquePtrs)
{
    using MyType = std::map<int32_t, std::tuple<int32_t, std::unique_ptr<MyObject>>>;

    MyType map1;
    MyType map2;

    map1.insert({ 1, std::make_tuple(2, std::make_unique<MyObject>()) });
    map2.insert({ 1, std::make_tuple(2, std::make_unique<MyObject>()) });

    ASSERT_EQ(map1, map2);
}

Obviously, when I run the above test (using Google Test), the test will fail, because the addresses are compared, instead of values (for which operator== and operator!= are provided).

Actual: { (1, (2, 4-byte object <B8-BF 86-01>)) }
Expected: map1
Which is: { (1, (2, 4-byte object <A0-BC 86-01>)) }
[FAILED]

I can manually iterate over each pair and de-reference each object, but I wonder if there is a more standardized solution for unique_ptr, given the fact that it is a basic construct.

Alexandru Irimiea
  • 2,513
  • 4
  • 26
  • 46
  • Perhaps you can try wrapping a tuple into a custom class that defines `operator==`. – vsoftco Dec 01 '15 at 23:15
  • What's the rationale behind using this instead of `tuple` ? – M.M Dec 02 '15 at 02:40
  • @M.M: `MyObject` happens to be a large object (I didn't include the fields here), so I want to avoid reallocation of these objects when modifying the map. – Alexandru Irimiea Dec 02 '15 at 09:18
  • @AlexandruIrimiea maps use a tree structure - the objects will remain in place even when modifying the map (or `move`ing the entire map) – M.M Dec 02 '15 at 10:11
  • 1
    @M.M: Well, that's interesting, I didn't know about that. I thought a reallocation logic similar to `std::vector` is implemented in `std::map`. I see this answer clarifies this matter: http://stackoverflow.com/a/516101/4806882 . In this case, I should use objects as values. – Alexandru Irimiea Dec 02 '15 at 10:49

1 Answers1

4

The most standard solution I can think of is just to define your own equality operator for that tuple:

bool operator==( const std::tuple<int32_t, std::unique_ptr<MyObject>> & a,
                 const std::tuple<int32_t, std::unique_ptr<MyObject>> & b )
{
    return std::get<0>(a) == std::get<0>(b) && *std::get<1>(a) == *std::get<1>(b);
}

This example might be enough for you, unless you want to also handle nullptr. If you use this same pattern for other tuples, it might make sense to template it:

template < class A, class B >
bool operator==( const std::tuple<A, std::unique_ptr<B>> & a,
                 const std::tuple<A, std::unique_ptr<B>> & b )
{
    return std::get<0>(a) == std::get<0>(b) && *std::get<1>(a) == *std::get<1>(b);
}
paddy
  • 60,864
  • 6
  • 61
  • 103
  • Note that I consciously didn't recommend defining `operator==` for `unique_ptr` by itself, even though that also works. I think it's better to overload behaviour for your specific case rather than overload behaviour for all `unique_ptr` cases. – paddy Dec 02 '15 at 00:11
  • `*` on a `unique_ptr` causes undefined behaviour if it's not currently managing an object; so unless OP's design guarantees all unique_ptr are not null, the code will need to have a null check. – M.M Dec 02 '15 at 02:39
  • @M.M yes, of course. I mentioned this in the answer, but omitted null-checks for the sake of clarity. – paddy Dec 02 '15 at 03:31