A note: this is an API design question, riding on the design of the constructors of unique_ptr
and share_ptr
for the sake of the question, but not aiming to propose any change to their current specifications.
Though it would usually be advisable to use make_unique
and make_shared
, both unique_ptr
and shared_ptr
can be constructed from a raw pointer.
Both get the pointer by value and copy it. Both allow (i.e. in the sense of: do not prevent) a continuance usage of the original pointer passed to them in the constructor.
The following code compiles and results with double free:
int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;
Both unique_ptr
and shared_ptr
could prevent the above if their relevant constructors would expect to get the raw pointer as an rvalue, e.g. for unique_ptr:
template<typename T>
class unique_ptr {
T* ptr;
public:
unique_ptr(T*&& p) : ptr{p} {
p = nullptr; // invalidate the original pointer passed
}
// ...
Thus, the original code would not compile as an lvalue cannot bind to an rvalue, but using std::move
the code compiles, while being more verbose and more secured:
int* ptr = new int(9);
std::unique_ptr<int> p { std::move(ptr) };
if (!ptr) {
// we are here, since ptr was invalidated
}
It is clear that there can be dozens of other bugs a user can do with smart pointers. The commonly used argument of you should know how to properly use the tools provided by the language, and C++ is not designed to watch over you etc.
But still, it seems that there could have been an option for preventing this simple bug and to encourage usage of make_shared
and make_unique
. And even before make_unique
was added in C++14, there is still always the option of direct allocation without a pointer variable, as:
auto ptr = std::unique_ptr<int>(new int(7));
It seems that requesting rvalue reference to a pointer as the constructor parameter could add a bit of an extra safety. Moreover, the semantics of getting rvalue seems to be more accurate as we take ownership of the pointer that is passed.
Coming to the question of why didn't the standard take this more secured approach?
A possible reason might be that the approach suggested above would prevent creating a unique_ptr
from const pointers, i.e. the following code would fail to compile with the proposed approach:
int* const ptr = new int(9);
auto p = std::unique_ptr { std::move(ptr) }; // cannot bind `const rvalue` to `rvalue`
But this seems to be a rare scenario worth neglecting, I believe.
Alternatively, in case the need to support initialization from a const pointer is a strong argument against the proposed approach, then a smaller step could still be achieved with:
unique_ptr(T* const&& p) : ptr{p} {
// ...without invalidating p, but still better semantics?
}
std::move
in case passing a pointer variable makes it much more verbose that there is a pass of ownership – Tabp
ceases to exist right after – Nationalityunique_ptr
(at the time calledmove_ptr
) here N1377 – Lovatoauto_ptr
tounique_ptr
" – Lovatop = nullptr
is setting p to a known value, not invalidation. It is still UB to delete an invalidated pointer – VikkiT* const&& p
and skip the invalidation, just to better express the move of ownership semantics – Tabunique_ptr
was designed to be as close to a drop in replacement forauto_ptr
as possible but without the "dangerous parts" thatauto_ptr
had (move with copy syntax). open-std.org/jtc1/sc22/wg21/docs/papers/2005/… – Claudetteclaudiaauto_ptr
couldn't of course be implemented with that approach. By the way, going withunique_ptr(T* const&& p) : ptr{p}
could have been a nice example for rvalue reference to const. – Tab