Raw Pointer Lookup for Sets of Unique_Ptrs

Raw pointer lookup for sets of unique_ptrs

In C++14, std::set<Key>::find is a template function if Compare::is_transparent exists. The type you pass in does not need to be Key, just equivalent under your comparator.

So write a comparator:

template<class T>
struct pointer_comp {
typedef std::true_type is_transparent;
// helper does some magic in order to reduce the number of
// pairs of types we need to know how to compare: it turns
// everything into a pointer, and then uses `std::less<T*>`
// to do the comparison:
struct helper {
T* ptr;
helper():ptr(nullptr) {}
helper(helper const&) = default;
helper(T* p):ptr(p) {}
template<class U, class...Ts>
helper( std::shared_ptr<U,Ts...> const& sp ):ptr(sp.get()) {}
template<class U, class...Ts>
helper( std::unique_ptr<U, Ts...> const& up ):ptr(up.get()) {}
// && optional: enforces rvalue use only
bool operator<( helper o ) const {
return std::less<T*>()( ptr, o.ptr );
}
};
// without helper, we would need 2^n different overloads, where
// n is the number of types we want to support (so, 8 with
// raw pointers, unique pointers, and shared pointers). That
// seems silly:
// && helps enforce rvalue use only
bool operator()( helper const&& lhs, helper const&& rhs ) const {
return lhs < rhs;
}
};

then use it:

typedef std::set< std::unique_ptr<Foo>, pointer_comp<Foo> > owning_foo_set;

now, owning_foo_set::find will accept unique_ptr<Foo> or Foo* or shared_ptr<Foo> (or any derived class of Foo) and find the correct element.

Outside of C++14, you are forced to use the map to unique_ptr approach, or something equivalent, as the signature of find is overly restrictive. Or write your own set equivalent.

Searching a set of unique pointers

You can use std::find_if like this:
std::find_if(numbers.begin(), numbers.end(), [&](std::unique_ptr<int>& p) { return p.get() == x;});

Is unique_ptr constructor initializes the raw pointer and also unique_ptr destructor deletes the associated raw pointer?

When creating unique_ptr object "p1" we are providing raw pointer. Internally, unique_ptr constructor will initialize the unique_ptr with the raw pointer. Is my understanding correct?

Yes. The unique pointer will hold the same address.

As per the unique_ptr definition, "The pointer is exclusively owned by one object or a resource".

Based on the above statement, in our scenario, "raw pointer" is exclusively owned by the unique_ptr object "p1". Am I correct?

Yes. The only reference, the one that owns the resource and will free it, is the unique pointer. Note however that it's not the pointer that's owned, but the object it points at. The unique_ptr didn't take ownership of the raw pointer, it took ownership of the object (the resource) that is at the address the raw pointer provided.

And also after the statement, cout << p1.get(); (In the above sample program) as it is going out of scope, internally, the destructor of the unique_ptr called and it deletes the associated raw pointer. Is my understanding correct?

Yes. The unique ptr will cause the deletion of its internal raw pointer when it goes out of scope.

Finally, once deletes the associated raw pointer is the unique_ptr object will become empty?

Doesn't have to. Since the deletion happens when the unique_ptr object itself is being destroyed, there is no real need to "empty" it. It's about to go out of existence anyway, so its value is immaterial.

Using std::less for unique_ptr to raw pointer comparison fails to compile, but works with wrapper?

Neither std::unique_ptr<int> nor int involve user-defined types; in terms of ADL their associated namespaces are namespace std only.

When you use a wrapper type you have an associated namespace of the root namespace (the namespace where you declared the wrapper), which means that ADL can find the free operator<s defined there.

Indeed, this is a Good Thing since it prevents someone else writing their own operator<(int*, std::unique_ptr<int> const&) which might have a different behavior to yours.

why the raw pointer get by std::unique_ptr's get() can not delete the object and how is that implemented

std::unique_ptr is a really simple class. Conceptually, it's basically just this:

template <typename T>
class unique_ptr
{
private:
T* ptr;

public:
unique_ptr(T* p) ptr{p} {}
~unique_ptr() { delete ptr; }

T* get() { return ptr; }
T* release() {
T* p = ptr;
ptr = nullptr;
return p;
}

// other stuff
};

It just has a pointer member, and deletes the pointed-to object in its destructor. In reality there's a bit more to it, but that's essentially it.

The get member just returns a copy of the unique_ptr's managed pointer. That's it. Since the unique_ptr's pointer member is still pointing to that object, its destructor will still delete the object. If you also delete that object via another pointer to it then it will get deleted twice, which results in undefined behavior.

The release member function, on the other hand, sets the unique_ptr's pointer member to nullptr before returning a copy of its original value. Since its member pointer is null, its destructor won't delete anything.

How to convert a function that returns unique_ptr into raw pointer?

A lambda function should work, e.g.:

LegacyFunction([](float a, float b) { return MyFunc(a, b).release(); }, 1, 2.0f);

but only if LegacyFunction will naturally delete/free the memory that MyFunc allocated with the appropriate operation (so if MyFunc used new int, LegacyFunction must use delete retval;; if it used new int[x], it must use delete[] retval;).

The release method gets the contained pointer and relinquishes ownership, so you've explicitly given up unique_ptr's protections; not a great idea if you can avoid it.

Map of unique pointers, .at() with a raw pointer

In C++ 14, you can provide a transparent comparator

template<typename T>
struct PtrCompare
{
std::less<T*> less;
using is_transparent = void;
bool operator()(T* lhs, const std::unique_ptr<T> & rhs) const { return less(lhs, rhs.get()); }
bool operator()(const std::unique_ptr<T> & lhs, T* rhs) const { return less(lhs.get(), rhs); }
bool operator()(const std::unique_ptr<T> & lhs, const std::unique_ptr<T> & rhs) const { return less(lhs.get(), rhs.get()); }
}

std::map<std::unique_ptr<SomeType>, SomeOtherType, PtrCompare<SomeType>> map;

That doesn't help with at, but does allow you to find based on anything that you can compare

SomeType* p = ...;
if (auto it = map.find(p))
{
// use it->second
}
else
{
throw std::out_of_range;
}


Related Topics



Leave a reply



Submit