Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep Comparison of unique_ptr in std::set while using operator==

Tags:

c++

c++11

I am trying to use std::set to hold a set of unique_ptr's to a custom object I have defined. I am providing a custom compare function while defining the set (to enable deep comparison). This compare function seems to be work correctly while inserting elements into the set i.e items with equivalent content are not inserted twice.

However, if I compare two sets using operator==, it seems to be ignored i.e sets with equivalent elements are returned as not equal, while I expect (would like) it to be equal (as the custom compare function I am providing does a deep compare).

Is the compare function only used during insertion? If so, is there an alternative to getting operator== to do a deep comparison?

Any pointers appreciated. Thanks :)

Sample code

//
//  main.cpp
//  Test

#include <iostream>
#include <set>

class Person {
private:
    std::string mName;

public:
    Person(const std::string& name);
    virtual ~Person() {}

    void setName(std::string& name);
    std::string getName();
};

typedef std::unique_ptr<Person> PersonUniquePtr;

Person::Person(const std::string& name)
    : mName{ name }
{
}

void Person::setName(std::string& name)
{
    mName = name;
}   

std::string Person::getName()
{
    return mName;
}

bool isLess(Person* p1, Person* p2)
{
    if (p1->getName().compare(p2->getName()) == -1)
        return true;

    return false;
}

struct PersonUniquePtr_less {
    bool operator()(PersonUniquePtr const& p1, PersonUniquePtr const& p2) const
    {
        return isLess(p1.get(), p2.get());
    }
};

int main(int argc, const char* argv[])
{
    std::set<PersonUniquePtr, PersonUniquePtr_less> personSet1;
    std::set<PersonUniquePtr, PersonUniquePtr_less> personSet2;

    PersonUniquePtr person1 = std::make_unique<Person>("Adam");
    PersonUniquePtr person2 = std::make_unique<Person>("Adam");
    personSet1.insert(std::move(person1));
    personSet1.insert(std::move(person2));
    std::cout << "personSet1.size(): " << personSet1.size() << std::endl; //Expected 1

    PersonUniquePtr person3 = std::make_unique<Person>("Bruce");
    personSet1.insert(std::move(person3));
    std::cout << "personSet1.size(): " << personSet1.size() << std::endl; //Expected 2

    PersonUniquePtr person4 = std::make_unique<Person>("Adam");
    PersonUniquePtr person5 = std::make_unique<Person>("Bruce");
    personSet2.insert(std::move(person4));
    personSet2.insert(std::move(person5));
    std::cout << "personSet2.size(): " << personSet2.size() << std::endl; //Expected 2

    std::cout << "PersonSet1:" << std::endl;
    for (auto& person : personSet1) {
        std::cout << person->getName() << std::endl;
    } //Prints out Adam Bruce

    std::cout << "PersonSet2:" << std::endl;
    for (auto& person : personSet2) {
        std::cout << person->getName() << std::endl;
    } //Prints out Adam Bruce

    bool setsAreEqual = (personSet1 == personSet2);
    if (setsAreEqual) {
        std::cout << "Sets are equal" << std::endl;
    } else {
        std::cout << "Sets are not equal" << std::endl;
    }

    return 0;
}
like image 249
georgemp Avatar asked Aug 28 '14 13:08

georgemp


People also ask

What is unique_ptr in C++?

(2) (since C++11) std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope.

What is the difference between shared_ptr and unique_ptr?

Unlike std::shared_ptr, std::unique_ptr may manage an object through any custom handle type that satisfies NullablePointer. This allows, for example, managing objects located in shared memory, by supplying a Deleter that defines typedef boost::offset_ptr pointer; or another fancy pointer .

Which operator compares a unique_ptr and nullptr?

8-20) Compares a unique_ptr and nullptr. The != operator is synthesized from operator== . The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

Can you return a unique_ptr from a pointer?

In general, you should not return std::unique_ptr by pointer (ever) or reference (unless you have a specific compelling reason to). If you want the function to take ownership of the contents of the pointer, pass the std::unique_ptr by value.


Video Answer


1 Answers

The C++11 container requirements say that a == b is equivalent to

distance(a.begin(), a.end()) == distance(b.begin(), b.end())
&& equal(a.begin(), a.end(), b.begin())

and std::equal does not use your custom comparison, it uses operator==

You can perform the comparison yourself by calling std::equal with a custom predicate:

a.size() == b.size()
&& std::equal(a.begin(), a.end(), b.begin(),
              [](PersonUniquePtr const& p1, PersonUniquePtr const& p2) {
                PersonUniquePtr_less cmp;
                return !cmp(p1, p2) && !cmp(p2, p1);
              });

In C++14 it's simpler because there's a new overload of std::equal taking four iterators, although as TemplateRex points out in a comment below, this is less efficient than testing a.size() == b.size() manually:

std::equal(a.begin(), a.end(), b.begin(), b.end(),
           [](PersonUniquePtr const& p1, PersonUniquePtr const& p2) {
             PersonUniquePtr_less cmp;
             return !cmp(p1, p2) && !cmp(p2, p1);
           });

In C++14 you can save some typing by using a polymorphic lambda:

std::equal(a.begin(), a.end(), b.begin(), b.end(),
           [](auto const& p1, auto const& p2) {
             PersonUniquePtr_less cmp;
             return !cmp(p1, p2) && !cmp(p2, p1);
           });
like image 58
Jonathan Wakely Avatar answered Oct 02 '22 07:10

Jonathan Wakely