diff --git a/homework/shared_ptr/CMakeLists.txt b/homework/shared_ptr/CMakeLists.txt new file mode 100644 index 00000000..10ef3fa8 --- /dev/null +++ b/homework/shared_ptr/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.11.0) +project(shared_ptr) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +enable_testing() + +add_executable(${PROJECT_NAME}_tests ${PROJECT_NAME}_tests.cpp) + +target_link_libraries(${PROJECT_NAME}_tests gtest_main) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}_tests) diff --git a/homework/shared_ptr/build.bat b/homework/shared_ptr/build.bat new file mode 100644 index 00000000..dc7cca9d --- /dev/null +++ b/homework/shared_ptr/build.bat @@ -0,0 +1,10 @@ +RMDIR build /S /Q +mkdir build +cd build +cmake -G "MinGW Makefiles" .. +MinGW32-make + +shared_ptr_tests.exe + +cd .. + diff --git a/homework/shared_ptr/format.bat b/homework/shared_ptr/format.bat new file mode 100644 index 00000000..5e62c1ff --- /dev/null +++ b/homework/shared_ptr/format.bat @@ -0,0 +1 @@ +clang-format --style=file -i *.hpp *.cpp \ No newline at end of file diff --git a/homework/shared_ptr/shared_ptr.hpp b/homework/shared_ptr/shared_ptr.hpp new file mode 100644 index 00000000..c7f16d2c --- /dev/null +++ b/homework/shared_ptr/shared_ptr.hpp @@ -0,0 +1,123 @@ +#pragma once + +namespace my { + +template +class shared_ptr { +private: + // Holds a pointer to managed object (template class) + T* obj_ptr; + // Holds a pointer to shared control block with 2 counters and a deleter: + // * shared_refs count (as `std::atomic`) + // * weak_refs count (as `std::atomic`) + // * deleter (function pointer) + struct ControlBlock { + std::atomic shared_refs; + std::atomic weak_refs; + void (*deleter)(T*); + + ControlBlock() + : shared_refs(1), weak_refs(0), deleter(nullptr) {} + }; + ControlBlock* control_block_ptr; + + void remove_this_pointer() { + if (control_block_ptr) { + control_block_ptr->shared_refs--; + if (0 == control_block_ptr->shared_refs) { + if (control_block_ptr->deleter) { + control_block_ptr->deleter(obj_ptr); + } else { + delete obj_ptr; + } + if (0 == control_block_ptr->weak_refs) { + delete control_block_ptr; + } + } + } + } + +public: + // Constructor: copies a pointer and allocate the control block + shared_ptr(T* pointer = nullptr) { + obj_ptr = pointer; + control_block_ptr = new ControlBlock(); + } + // Destructor: decrease shared_refs and: + // - if shared_refs == 0 -> release the managed object + // - if shared_refs == 0 and weak_refs == 0 -> release the control block + ~shared_ptr() { + remove_this_pointer(); + } + // Copying is allowed - it increments shared_refs + shared_ptr(const shared_ptr& other) { + obj_ptr = other.obj_ptr; + control_block_ptr = other.control_block_ptr; + other.control_block_ptr->shared_refs++; + } + shared_ptr& operator=(const shared_ptr& other) { + remove_this_pointer(); + obj_ptr = other.obj_ptr; + control_block_ptr = other.control_block_ptr; + if (control_block_ptr) + control_block_ptr->shared_refs++; + return *this; + } + // Moving is allowed and it means: + // * Copying original pointers to a new object + // * Setting source pointer to nullptr + shared_ptr(shared_ptr&& other) + : obj_ptr(other.obj_ptr), control_block_ptr(other.control_block_ptr) { + other.obj_ptr = nullptr; + other.control_block_ptr = nullptr; + } + shared_ptr& operator=(shared_ptr&& other) { + remove_this_pointer(); + + obj_ptr = other.obj_ptr; + control_block_ptr = other.control_block_ptr; + + other.obj_ptr = nullptr; + other.control_block_ptr = nullptr; + + return *this; + } + + size_t use_count() { + if (control_block_ptr) { + return control_block_ptr->shared_refs; + } else { + return 0; + } + } + + T& operator*() { + return *obj_ptr; + } + + T* operator->() { + return obj_ptr; + } + + explicit operator bool() const { + return obj_ptr != nullptr; + } + + T* get() { + return obj_ptr; + } + + void reset(T* new_ptr = nullptr) { + if (obj_ptr != new_ptr) { + remove_this_pointer(); + obj_ptr = new_ptr; + if (new_ptr) { + control_block_ptr = new ControlBlock(); + } else { + control_block_ptr = nullptr; + } + } + } +}; + +} // namespace my diff --git a/homework/shared_ptr/shared_ptr_tests.cpp b/homework/shared_ptr/shared_ptr_tests.cpp new file mode 100644 index 00000000..1494ff4d --- /dev/null +++ b/homework/shared_ptr/shared_ptr_tests.cpp @@ -0,0 +1,124 @@ +#include +#include "shared_ptr.hpp" + +// Instantiate the template class for testing +template class my::shared_ptr; + +TEST(SharedPtrTest, DefaultConstructor) { + my::shared_ptr sp; + EXPECT_EQ(sp.use_count(), 1); + EXPECT_FALSE(sp); +} + +TEST(SharedPtrTest, ConstructorWithPointer) { + my::shared_ptr sp(new int(42)); + EXPECT_EQ(sp.use_count(), 1); + EXPECT_TRUE(sp); + EXPECT_EQ(*sp, 42); +} + +TEST(SharedPtrTest, ArrowOperator) { + struct TestStruct { + int value; + TestStruct(int v) + : value(v) {} + }; + + my::shared_ptr sp(new TestStruct(42)); + EXPECT_EQ(sp->value, 42); + sp->value = 24; + EXPECT_EQ(sp->value, 24); +} + +TEST(SharedPtrTest, GetMethod) { + int* raw_ptr = new int(10); + my::shared_ptr sp(raw_ptr); + + EXPECT_EQ(sp.get(), raw_ptr); + EXPECT_EQ(*sp.get(), 10); + + EXPECT_EQ(sp.use_count(), 1); +} + +TEST(SharedPtrTest, ResetMethod) { + my::shared_ptr sp(new int(5)); + EXPECT_EQ(*sp, 5); + EXPECT_EQ(sp.use_count(), 1); + + sp.reset(new int(10)); + EXPECT_EQ(*sp, 10); + EXPECT_EQ(sp.use_count(), 1); + + sp.reset(); + EXPECT_EQ(sp.get(), nullptr); + EXPECT_EQ(sp.use_count(), 0); +} + +TEST(SharedPtrTest, ResetWithMultipleReferences) { + my::shared_ptr sp1(new int(5)); + my::shared_ptr sp2 = sp1; + + EXPECT_EQ(sp1.use_count(), 2); + EXPECT_EQ(sp2.use_count(), 2); + + sp1.reset(new int(10)); + EXPECT_EQ(*sp1, 10); + EXPECT_EQ(sp1.use_count(), 1); + EXPECT_EQ(*sp2, 5); + EXPECT_EQ(sp2.use_count(), 1); +} + +TEST(SharedPtrTest, CopyConstructor) { + my::shared_ptr sp1(new int(42)); + my::shared_ptr sp2(sp1); + EXPECT_EQ(*sp1, 42); + EXPECT_EQ(*sp2, 42); + EXPECT_EQ(sp1.use_count(), 2); + EXPECT_EQ(sp2.use_count(), 2); +} + +TEST(SharedPtrTest, CopyAssignment) { + my::shared_ptr sp1(new int(42)); + my::shared_ptr sp2; + sp2 = sp1; + EXPECT_EQ(*sp1, 42); + EXPECT_EQ(*sp2, 42); + EXPECT_EQ(sp1.use_count(), 2); + EXPECT_EQ(sp2.use_count(), 2); +} + +TEST(SharedPtrTest, MoveConstructor) { + my::shared_ptr sp1(new int(42)); + my::shared_ptr sp2(std::move(sp1)); + EXPECT_EQ(*sp2, 42); + EXPECT_EQ(sp2.use_count(), 1); + EXPECT_FALSE(sp1); +} + +TEST(SharedPtrTest, MoveAssignment) { + my::shared_ptr sp1(new int(42)); + my::shared_ptr sp2; + sp2 = std::move(sp1); + EXPECT_EQ(*sp2, 42); + EXPECT_EQ(sp2.use_count(), 1); + EXPECT_FALSE(sp1); +} + +TEST(SharedPtrTest, BoolOperator) { + my::shared_ptr sp1(new int(42)); + my::shared_ptr sp2; + EXPECT_TRUE(sp1); + EXPECT_FALSE(sp2); +} + +TEST(SharedPtrTest, MultipleSharedPtrs) { + my::shared_ptr sp1(new int(42)); + { + my::shared_ptr sp2 = sp1; + my::shared_ptr sp3 = sp1; + EXPECT_EQ(sp1.use_count(), 3); + EXPECT_EQ(sp2.use_count(), 3); + EXPECT_EQ(sp3.use_count(), 3); + } + EXPECT_EQ(sp1.use_count(), 1); +} \ No newline at end of file