#include #include /// Holds unique ownership of a pointed to object and destroys it automatically template class UniquePtr { public: /// Constructor without custom deleter - use default deleter which just calls 'delete' explicit UniquePtr(T *object) : object(object), deleter([](T *object) -> void { delete object; }) { assert(object != nullptr); } /// Constructor with custom deleter /// Note that the custom deleter may not get a valid object (it could be a nullptr due to resetting, moving, etc) UniquePtr(T *object, void (*deleter)(T *)) : object(object), deleter(deleter) { assert(object != nullptr); assert(deleter != nullptr); } /// Copy constructor /// Shouldn't be used, since the pointer wouldn't be unique otherwise UniquePtr(const UniquePtr &other) = delete; /// Move constructor UniquePtr(UniquePtr &&other) : object(other.object), deleter(other.deleter) { other.object = nullptr; } /// Copy assignment operator /// Shouldn't be used, since the pointer wouldn't be unique otherwise UniquePtr &operator=(const UniquePtr &) = delete; /// Move assignment operator UniquePtr &operator=(UniquePtr &&other) { Swap(other.object); deleter = other.deleter; // TODO: If Swap is implemented with std::swap instead of deleting the current object, this should not be done! // However, that would mean that the old object can stay in memory unnecessarily long (until 'other' goes out of scope) other.object = nullptr; } /// Destructor virtual ~UniquePtr() { // TODO: deleter(object) is called twice if 'Reset()' was previously called and the UniquePtr goes out of scope. // With the default deleter, this is fine since 'delete object' handles the case of the object being null. // However, with a custom deleter this may not be intended? // We'd have to introduce a new flag and an if statement here deleter(object); } T operator*() const { assert(object != nullptr); return *object; } operator bool() const { return object != nullptr; } T *operator->() const { assert(object != nullptr); return object; } /// Release ownership of the owned object and return the pointer to it T *Release() { T *returnObject = object; object = nullptr; return returnObject; } /// Reset the internal pointer to null, deleting the owned object void Reset() { deleter(object); object = nullptr; } /// Swaps the currently owned object with another void Swap(T *other) { // TODO: This isn't really a "swap" but more of a "replace" - a proper swap would be std::swap(object, other); // Not sure which one should be implemented, since I find this version more practical... deleter(object); object = other; } private: T *object; void (*deleter)(T *); }; /// Simple object for testing constructor, destructor calls and potential memory leaks class TestObject { public: TestObject() { std::cout << "Constructor" << std::endl; instanceCount += 1; } TestObject(const TestObject &other) { std::cout << "Copy constructor" << std::endl; } ~TestObject() { std::cout << "Destructor" << std::endl; instanceCount -= 1; } static void testPrint() { std::cout << "testPrint" << std::endl; } static int getInstanceCount() { return instanceCount; } private: static int instanceCount; }; int TestObject::instanceCount = 0; void customTestObjectDeleter(TestObject *object) { std::cout << "Custom deleter" << std::endl; delete object; } int main() { { std::cout << "Constructing first pointer" << std::endl; UniquePtr pointer = UniquePtr(new TestObject()); std::cout << "Calling test print via pointer if it bools to true" << std::endl; if (pointer) { pointer->testPrint(); } std::cout << "Swapping for new object" << std::endl; pointer.Swap(new TestObject()); std::cout << "Move assigning new pointer" << std::endl; UniquePtr pointer2 = UniquePtr(std::move(pointer)); std::cout << "Constructing pointer with custom deleter" << std::endl; UniquePtr pointer3 = UniquePtr(new TestObject(), customTestObjectDeleter); std::cout << "Resetting that pointer" << std::endl; pointer3.Reset(); if (pointer3) { std::cout << "This shouldn't happen!" << std::endl; } std::cout << "Moving into a pointer which already has an object" << std::endl; UniquePtr pointer4 = UniquePtr(new TestObject()); UniquePtr pointer5 = UniquePtr(new TestObject()); pointer4 = std::move(pointer5); } std::cout << std::endl << "All pointers are now out of scope, all objects should be deleted!" << std::endl; std::cout << TestObject::getInstanceCount() << " instances left" << std::endl; return 0; }