Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't std::unique_ptr implicitly convert to T* and const T*?

Tags:

c++

c++11

If I were to write it myself, I guess I'd do something like:

template<typename T, typename Dtor = std::default_delete<T> >
class Uptr : private Dtor {
  T* vl_;
public:
  explicit Uptr(T* vl = nullptr) noexcept : vl_(vl) {}
  ~Uptr() noexcept { Dtor::operator()(vl_); }

  Uptr& swap(Uptr& o) noexcept { T* tmp; tmp = vl_; vl_=o.vl_; o.vl_ = tmp; }

  Uptr& operator=(Uptr&& o) noexcept { o.swap(*this); }
  Uptr& operator=(nullptr_t) noexcept { vl_=nullptr; return *this; } 
  Uptr(Uptr&& o) noexcept : Uptr(nullptr) { *this = std::move(o); } 

  Uptr(const Uptr& o) = delete;
  Uptr& operator=(const Uptr& o) = delete;


  operator T*() noexcept { return vl_; } 
  operator const T*() const noexcept { return vl_; } 

  T* release() noexcept { T* ret = vl_; vl_=nullptr; return ret; }    

  const Dtor& deleter() const noexcept { return *(static_cast<Dtor*>(this)); }
  Dtor& deleter() noexcept { return *(static_cast<Dtor*>(this)); }
};

And save myself from having to define get() and operators *, ->, and [].

What's wrong with having implicit conversions in this case?

like image 468
PSkocik Avatar asked Feb 15 '16 12:02

PSkocik


3 Answers

I don't think your question is specific to unique_ptr, but rather asking about smart pointers in general.

Herb Sutter wrote about this a long time ago. Apparently, it would allow you to write logically-erroneous code such as:

unique_ptr<something> p;
...
delete p; // p is a smart pointer - probably not what you want.

and other code like that.

like image 83
Ami Tavory Avatar answered Nov 10 '22 14:11

Ami Tavory


There are some annoying errors that might happen, as others have pointed out, like

T* f() { unique_ptr p{...}; return p; } // oops

unique_ptr p{...}; delete p; // oops

and

unique_ptr get_up();

shared_ptr sp{get_up()}; // cool, this works: promote UP to SP

// does it work the other way around?

shared_ptr get_sp();

unique_ptr p{get_sp()}; // compiles, then yes! eh, wait.. BOOM!

And so on. It's just safer, considering that that language is designed for humans, to dodge these pitfalls by making everything explicit in such cases.

like image 20
Yam Marcovic Avatar answered Nov 10 '22 12:11

Yam Marcovic


I have no formal answer, but I have an idea why this would not make much sense. The whole idea of unique_ptr is to be a strongly typed (i.e. compiler-enforced) way to carry unique ownership of an object. Hence the move semantics.

In that context, it makes sense to have the unique_ptr require you to explicitly request its raw pointer, because as soon as you have the raw value, you can easily break everything: you could delete it, you could pass it to another smart pointer (which would in turn delete it at some point), or do pointer arithmetics and accidentally work with the result (which might not point to allocated memory) or anything like that. Some of this may immediately lead to compiler errors, others may lead to undefined behaviour. For a high-level class such as unique_ptr, this is not desirable, at least not this implicitly. Of course you can do the same using .get(), but in that case, you know that you are using the raw pointer value and that you must not free it.

The link posted by Ami is also very relevant, but I think a different example is better quoted:

unique_ptr p( new widget );
...
use( p + 42 ); // error (maybe he meant "*p + 42"?)
    // but if implicit conversion to * were allowed, would silently compile -- urk

The C++ philosophy is that public functions which can operate on a given type belong to the interface of that type. Controlling and restricting the interface of a type which implicitly converts to a pointer is virtually impossible. The intentionally restrictive nature of smart pointers would be entirely deconstructed, leading to error prone code.

like image 6
Jonas Schäfer Avatar answered Nov 10 '22 13:11

Jonas Schäfer