Use the Swap Idiom to Move Instances
Among other benefits, swaps can be thread-safe atomic operations that never throw exceptions.
About That Converting Constructor
(Thanks to @chrysante for pointing out a serious oversight in my original answer.)
One important piece of functionality here is the ability to do things like:
std::unique_ptr<Base> foo = std::make_unique<Derived>(bar, baz);
You implement this with the converting constructor
// Added this to allow for runtime polymorphism, but I didn't
// see such a constructor on cppreference. Am I just missing something
// or should this be handled another way?
template<typename U,
std::enable_if_t<std::is_convertible_v<U*, T*>, bool> = false>
UniquePtr(UniquePtr<U>&& other) noexcept :
m_ptr{other.release()}
{}
First, in a reinventing-the-wheel project, you get to use requires
from C++20, which is much nicer than std::enable_if
. So I recommend that.
Second, this implementation will break if the pointer conversion changes the bits of the pointer, even if it is from a derived class to a base class with a virtual
destructor. This is because not all pointer conversions are pointer-interconvertible. That is, some pointer conversions (such as multiple or virtual inheritance) produce a pointer with different bits. Calling the original deleter, which expects a T*
and not a U*
, with the converted U*
, would then fail. The current approach is only safe in two cases:
- Where
std::is_pointer_interconvertible_base_class<T,U>
istrue
and the destructor ofT
is eithervirtual
or trivial, or - Where
std::is_layout_compatible<T,U>
,std::is_trivially_destructible<T>
andstd::is_trivially_destructible<U>
are all true. (The standard does not saystd::unique_ptr
should support this case.)
At present, however, the implementation supports this for all possible conversions, including arbitrary one-way custom conversion operators. The only way to make this work robustly would be to store the closure of the deleter and the original pointer inside each instance.
If you aren’t going to go through all that, you could declare non-pointer-interconvertible aliasing unsupported, add a check for that to your restrict
clauses, and keep the simple, high-performance implementation you have now. In that case, you want to declare your deleter with the [[no_unique_address]]
attribute, so that the vast majority of deleters, which are unit types whose instance contain nothing, don’t need to be created and stored as objects.
Otherwise, you could put a/*** BIG IMPORTANT WARNING ***/
in your header file that people are only supposed to pass in a deleter that still works, somehow, with every possible conversion to a base pointer. And then say, the bugs this causes are on them for not reading the documentation.
Finally, since you ask, the converting constructor appears to be number 6 on Cppreference, as of July 2023.