Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Raw pointer lookup for sets of unique_ptrs

I often find myself wanting to write code like this:

class MyClass { public:   void addObject(std::unique_ptr<Object>&& newObject);    void removeObject(const Object* target);  private:   std::set<std::unique_ptr<Object>> objects; }; 

However, much of the std::set interface is kind of useless with std::unique_ptrs since the lookup functions require std::unique_ptr parameters (which I obviously don't have because they're owned by the set itself).

I can think of two main solutions to this.

  1. Create a temporary unique_ptr for lookup. For example, the above removeObject() could be implemented like:

    void MyClass::removeObject(const Object* target) {   std::unique_ptr<Object> targetSmartPtr(target);   objects.erase(targetSmartPtr);   targetSmartPtr.release(); } 
  2. Replace the set with a map of raw pointers to unique_ptrs.

      // ...   std::map<const Object*, std::unique_ptr<Object>> objects; }; 

However, both seem slightly stupid to me. In solution 1, erase() isn't noexcept, so the temporary unique_ptr might delete the object it doesn't really own, and 2 requires double the storage for the container unnecessarily.

I know about Boost's pointer containers, but their current features are limited compared to modern C++11 standard library containers.

I was recently reading about C++14 and came across "Adding heterogeneous comparison lookup to associative containers". But form my understanding of it, the lookup types must be comparable to the key types, but raw pointers aren't comparable to unique_ptrs.

Anyone know of a more elegant solution or an upcoming addition to C++ that solves this problem?

like image 370
jbatez Avatar asked Sep 22 '13 03:09

jbatez


People also ask

What is a raw pointer?

A raw pointer is a pointer whose lifetime isn't controlled by an encapsulating object, such as a smart pointer. A raw pointer can be assigned the address of another non-pointer variable, or it can be assigned a value of nullptr . A pointer that hasn't been assigned a value contains random data.

What happens when you return a unique_ ptr?

If a function returns a std::unique_ptr<> , that means the caller takes ownership of the returned object. class Base { ... }; class Derived : public Base { ... }; // Foo takes ownership of |base|, and the caller takes ownership of the returned // object.

Can two unique pointers point to same address?

Pointers: Pointing to the Same AddressThere is no limit on the number of pointers that can hold (and therefore point to) the same address.


2 Answers

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.

like image 185
Yakk - Adam Nevraumont Avatar answered Sep 19 '22 18:09

Yakk - Adam Nevraumont


Another possibility, close to the accepted answer, but a little different and simplified.

We can exploit the fact that standard comparator std::less<> (with no template arguments) is transparent. Then, we can supply our own comparison functions in the global namespace:

// These two are enough to be able to call objects.find(raw_ptr) bool operator<(const unique_ptr<Object>& lhs, const Object* rhs) {   return std::less<const Object*>()(lhs.get(), rhs); } bool operator<(const Object* lhs, const unique_ptr<Object>& rhs) {   return std::less<const Object*>()(lhs, rhs.get()); }  class MyClass {   // ...  private:   std::set<std::unique_ptr<Object>, std::less<>> objects;  // Note std::less<> here }; 
like image 34
Mikhail Avatar answered Sep 21 '22 18:09

Mikhail