Sanity Checking an unordered_set of unique_ptrs to objects
Consider specializing standard templates

If the key fields of your structure are general, wherever it is used (rather than being a subset chosen for this particular set), then you could specialize std::hash and std::equal_to instead of declaring your own functors:

#include &lt;functional&gt;
namespace std
{
    template&lt;&gt;
    std::size_t hash&lt;TestPtr&gt;::operator()(TestPtr const&amp; s) const
    {
        if (!s) return 0;
        auto h1 = std::hash&lt;std::string&gt;{}(s-&gt;strVal);
        auto h2 = std::hash&lt;int&gt;{}(s-&gt;intVal);
        auto h3 = std::hash&lt;int&gt;{}(s-&gt;intVal2);
        return h1 ^ (h2 &lt;&lt; 1) ^ (h3 &lt;&lt; 2);
    }

    template&lt;&gt;
    bool equal_to&lt;TestPtr&gt;::operator()(TestPtr const&amp; lhs, TestPtr const&amp; rhs) const
    {
        return lhs
            &amp;&amp; rhs
            &amp;&amp; lhs-&gt;strVal == rhs-&gt;strVal
            &amp;&amp; lhs-&gt;intVal == rhs-&gt;intVal
            &amp;&amp; lhs-&gt;intVal2 == rhs-&gt;intVal2;
    }
}

using TestSet = std::unordered_set&lt;TestPtr&gt;;


Notes on the test program

I prefer to separate the test code (in Real Life, you'd put it in a separate file):

#include &lt;functional&gt;
#include &lt;memory&gt;
#include &lt;string&gt;
#include &lt;unordered_set&gt;

struct TestRecord
{
   // and the rest of the production code
};

// --- test code follows

#include &lt;iostream&gt;
#include &lt;list&gt;
bool DoThings(const std::string context, const TestPtr&amp; item)
{
   //
   return true;
}

int main()
{
   // Note: omitted arguments, which weren't used
}


When you move the production code to a header, you don't have to strip out the unnecessary includes.

The return value of DoThings() is ignored; either use it, or return void.

When inserting, it's more idiomatic to call insert() rather than a separate find() step.  The return value tells you whether your value was inserted - if it wasn't, you also get an iterator to assign to.  However, as you've observed, you aren't able to perform the assignment if you passed your value by rvalue reference.  Still, I'd try to encapsulate this in a method:

auto insert_or_assign(TestSet&amp; set, TestPtr&amp;&amp; value)
    -&gt; std::pair&lt;TestSet::iterator, bool&gt;
{
    auto overwrite = set.erase(value);
    auto r = set.insert(std::move(value));
    r.second = !overwrite;
    return r;
}


You can then use it like this:

int main()
{
    std::vector&lt;TestPtr&gt; thisList;

    thisList.emplace_back(new TestRecord{ "name", 1, 2, "Other" });
    thisList.emplace_back(new TestRecord{ "name2", 1, 2, "Less" });
    thisList.emplace_back(new TestRecord{ "name", 3, 2, "Is" });
    thisList.emplace_back(new TestRecord{ "name", 1, 5, "More" });
    thisList.emplace_back(new TestRecord{ "name2", 1, 2, "More" });

    TestSet thisSet;
    for (auto&amp; t: thisList) {
        auto inserted = insert_or_assign(thisSet, std::move(t));
        DoThings(inserted.second ? "new node" : "replace", *inserted.first);
    }
}