cpp-uniqueptr/main.cpp
2019-11-30 21:38:09 +01:00

174 lines
5.2 KiB
C++

#include <iostream>
#include <cassert>
/// Holds unique ownership of a pointed to object and destroys it automatically
template<class T>
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;
return *this;
}
/// 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<TestObject> pointer = UniquePtr<TestObject>(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<TestObject> pointer2 = UniquePtr<TestObject>(std::move(pointer));
std::cout << "Constructing pointer with custom deleter" << std::endl;
UniquePtr<TestObject> pointer3 = UniquePtr<TestObject>(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<TestObject> pointer4 = UniquePtr<TestObject>(new TestObject());
UniquePtr<TestObject> pointer5 = UniquePtr<TestObject>(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;
}