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 itnoexceptfor 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\$