Wrote a couple of blog articles about smart pointers.
So I suppose it time to get the result reviewed.
This is not supposed to be a replacement for the standard smart pointers. It is more a simple place to start for people trying to learn some of the basics. I am trying to show of the basic things you have to do in order to get one off the ground as a learning processes.
I suppose one of the main things missing is handling arrays.
#include <cstddef>
#include <utility>
namespace Loki
{
template<typename T>
class UniquePtr
{
T* data;
public:
// Default constructor.
UniquePtr()
: data(nullptr)
{}
// Take ownership of a pointer
// Note: Do this explicitly to avoid accidental conversion
// If we allowed accidental conversion while passing to a function.
// The pointer would be deleted on function return so an unsuspecting
// user would then be using a deleted pointer. So we must make
// taking ownership be explicit.
explicit UniquePtr(T* data)
: data(data)
{}
// Disable copying.
UniquePtr(UniquePtr const&) = delete;
UniquePtr& operator=(UniquePtr const&) = delete;
// Allow move
UniquePtr(UniquePtr&& move)
: data(nullptr)
{
// Note: data must be null before the swap
std::swap(data, move.data);
}
UniquePtr& operator=(UniquePtr&& move)
{
move.swap(*this);
return *this;
}
// Allow the use of `nullptr directly
// Because of the explicit above you can not pass a nullptr to functions
// that take a UniquePtr as an argument you have to explicitly create the
// UniquePtr to pass. `nullptr` is a unique case and it is safe to
// dynamically create the UniquePtr on the fly. This constructor allows this.
UniquePtr(std::nullptr_t)
: data(nullptr)
{}
// Conversion Constructors.
// Note: These will only compile if U is derived from T
template<typename U>
UniquePtr(U* data)
: data(data)
{}
template<typename U>
UniquePtr(UniquePtr<U>&& move)
: data(nullptr)
{
move.swap(*this);
}
template<typename U>
UniquePtr operator=(UniquePtr<U>&& move)
{
// Note:
// We can not swap *this and move.
// Because what we have stored locally may not be a U
// so it can not be moved to a U. So we must store that
// locally in old (this making this->data = nullptr)
UniquePtr<T> old(std::move(*this));
// Now that this->data is a nullptr we can swap them.
move.swap(*this);
return *this;
}
// And of course delete when it goes out of scope.
~UniquePtr()
{
delete [] data;
}
void swap(UniquePtr& other) noexcept
{
using std::swap;
swap(data, other.data);
}
// Release the data from ownership.
T* release()
{
T* result = nullptr;
std::swap(result,data);
return result;
}
// Common operations performed on a uniquePtr
// Note the state of `data` does not affect the state of the
// smart pointer.
T& operator&() const {return *data;}
T* operator->() const {return data;}
// Common tests
bool isEmpty() const {return data;}
operator bool()const {return data;}
// Just get raw pointer.
T* get() const {return data;} // Note the method is const
// even though we don't return a const pointer.
// This is deliberate. We are allowing raw access
// to the data that does not involve the objects
// ownership.
};
}
int main()
{
Loki::UniquePtr<int> data(new int(5));
}
// Done
T& operator&()
should beT& operator*()
. \$\endgroup\$noexcept
. They still work if they are not declared this way. But the standard library get to perform a whole bunch more optimizations of you make sure the move constructor/assignemnt operator arenoexcept
. \$\endgroup\$if (noexcept(move)) { move; } else if (copy) { copy; } else { move; }
. Since you provide no copy operations, you're forcing a move, but losing strong exception safety (but not really, because your move can't throw). Although you might still mark itnoexcept
for documentation and symmetry with other classes that are copyable. Quick edit: I changed my mind; using noexcept can do no harm, so you should probably use it just to future-proof the class. \$\endgroup\$