diff --git a/homework/CMakeLists.txt b/homework/CMakeLists.txt new file mode 100644 index 00000000..61b37541 --- /dev/null +++ b/homework/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.10) +project(SharedPtrProject) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +#compile options +add_compile_options(-Wall -Wextra -Werror -pedantic) + +#include header files +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +#GTest include +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.12.1 +) +FetchContent_MakeAvailable(googletest) + +#Test executable +add_executable(shared_ptr_tests shared_ptr/shared_ptr_tests.cpp) +add_executable(weak_ptr_tests shared_ptr/weak_ptr_tests.cpp) +add_executable(make_shared_tests shared_ptr/make_shared_tests.cpp) + +#Link GTest +target_link_libraries(shared_ptr_tests GTest::gtest GTest::gtest_main) +target_link_libraries(weak_ptr_tests GTest::gtest GTest::gtest_main) +target_link_libraries(make_shared_tests GTest::gtest GTest::gtest_main) + +enable_testing() + +add_test(NAME SharedPtrTests COMMAND shared_ptr_tests) +add_test(NAME WeakPtrTests COMMAND weak_ptr_tests) +add_test(NAME MakeSharedTests COMMAND make_shared_tests) \ No newline at end of file diff --git a/homework/shared_ptr/make_shared.hpp b/homework/shared_ptr/make_shared.hpp new file mode 100644 index 00000000..db5e1b03 --- /dev/null +++ b/homework/shared_ptr/make_shared.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include "shared_ptr.hpp" + +namespace my { + +template +shared_ptr make_shared(Args&&... args) { + using CombinedBlock = typename shared_ptr::ControlBlockWithObject; + auto* combined = new CombinedBlock(std::forward(args)...); + + shared_ptr result(&combined->object, &combined->control); + result.control_block->deleter = [](T* ptr) { + auto* combined_ptr = reinterpret_cast( + reinterpret_cast(ptr) - offsetof(CombinedBlock, object)); + delete combined_ptr; + }; + + return result; +} + +} // namespace my \ No newline at end of file diff --git a/homework/shared_ptr/make_shared_tests.cpp b/homework/shared_ptr/make_shared_tests.cpp new file mode 100644 index 00000000..9d0b5927 --- /dev/null +++ b/homework/shared_ptr/make_shared_tests.cpp @@ -0,0 +1,36 @@ +#include +#include +#include "make_shared.hpp" +#include "weak_ptr.hpp" + +struct TestStruct { + int x; + std::string str; + + TestStruct(int x_, std::string str_) + : x(x_), str(std::move(str_)) {} +}; + +TEST(MakeSharedTest, BasicType) { + auto sp = my::make_shared(42); + EXPECT_EQ(*sp, 42); + EXPECT_EQ(sp.use_count(), 1); +} + +TEST(MakeSharedTest, CustomType) { + auto sp = my::make_shared(42, "test"); + EXPECT_EQ(sp->x, 42); + EXPECT_EQ(sp->str, "test"); + EXPECT_EQ(sp.use_count(), 1); +} + +TEST(MakeSharedTest, WeakPtr) { + auto sp = my::make_shared(42); + my::weak_ptr wp(sp); + + EXPECT_EQ(wp.use_count(), 1); + EXPECT_FALSE(wp.expired()); + + auto locked = wp.lock(); + EXPECT_EQ(*locked, 42); +} \ 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..3ddb836d --- /dev/null +++ b/homework/shared_ptr/shared_ptr.hpp @@ -0,0 +1,127 @@ +#pragma once +#include +#include + +namespace my { + +template +class weak_ptr; // Forward declaration +template +class shared_ptr; // Forward declaration +template +shared_ptr make_shared(Args&&... args); + +template +class shared_ptr { +private: + T* ptr; + + struct ControlBlock { + std::atomic shared_count; + std::atomic weak_count; + void (*deleter)(T*); + + ControlBlock() + : shared_count(1), weak_count(0), deleter(nullptr) {} + }; + + struct ControlBlockWithObject { + ControlBlock control; + T object; + + template + ControlBlockWithObject(Args&&... args) + : control(), object(std::forward(args)...) {} + }; + + ControlBlock* control_block; + + // Private constructor for weak_ptr::lock() and make_shared + shared_ptr(T* p, ControlBlock* cb) noexcept + : ptr(p), control_block(cb) {} + + friend class weak_ptr; + template + friend shared_ptr make_shared(Args&&... args); + +public: + explicit shared_ptr(T* p = nullptr) + : ptr(p), control_block(new ControlBlock()) {} + + shared_ptr(const shared_ptr& other) + : ptr(other.ptr), control_block(other.control_block) { + if (control_block) { + ++control_block->shared_count; + } + } + + shared_ptr& operator=(const shared_ptr& otherSharedPtr) { + shared_ptr(otherSharedPtr).swap(*this); + return *this; + } + + shared_ptr(shared_ptr&& otherSharedPtr) noexcept + : ptr(otherSharedPtr.ptr), control_block(otherSharedPtr.control_block) { + otherSharedPtr.ptr = nullptr; + otherSharedPtr.control_block = nullptr; + } + + shared_ptr& operator=(shared_ptr&& otherSharedPtr) noexcept { + shared_ptr(std::move(otherSharedPtr)).swap(*this); + return *this; + } + + ~shared_ptr() { + if (control_block) { + if (--control_block->shared_count == 0) { + if (control_block->deleter) { + control_block->deleter(ptr); + control_block = nullptr; + ptr = nullptr; + } else { + delete ptr; + if (control_block->weak_count == 0) { + delete control_block; + } + control_block = nullptr; + ptr = nullptr; + } + } + } + } + + T& operator*() const { return *ptr; } + T* operator->() const { return ptr; } + T* get() const { return ptr; } + + size_t use_count() const { + return (control_block ? control_block->shared_count.load() : 0); + } + + explicit operator bool() const { return ptr != nullptr; } + + void swap(shared_ptr& otherSharedPtr) noexcept { + std::swap(ptr, otherSharedPtr.ptr); + std::swap(control_block, otherSharedPtr.control_block); + } + + void reset(T* p = nullptr) { + if (p != ptr) { + if (control_block) { + if (--control_block->shared_count == 0) { + delete ptr; + if (control_block->weak_count == 0) { + delete control_block; + } + } + } + ptr = p; + if (p) { + control_block = new ControlBlock(); + } else { + control_block = nullptr; + } + } + } +}; +} // namespace my \ No newline at end of file diff --git a/homework/shared_ptr/shared_ptr_tests.cpp b/homework/shared_ptr/shared_ptr_tests.cpp new file mode 100644 index 00000000..f6e6f26e --- /dev/null +++ b/homework/shared_ptr/shared_ptr_tests.cpp @@ -0,0 +1,131 @@ +#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, Swap) { + my::shared_ptr sp1(new int(42)); + my::shared_ptr sp2(new int(24)); + sp1.swap(sp2); + EXPECT_EQ(*sp1, 24); + EXPECT_EQ(*sp2, 42); +} + +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 diff --git a/homework/shared_ptr/weak_ptr.hpp b/homework/shared_ptr/weak_ptr.hpp new file mode 100644 index 00000000..9ccf2173 --- /dev/null +++ b/homework/shared_ptr/weak_ptr.hpp @@ -0,0 +1,94 @@ +#pragma once +#include +#include "shared_ptr.hpp" + +namespace my { + +template +class weak_ptr { +private: + T* ptr{nullptr}; + typename shared_ptr::ControlBlock* control_block{nullptr}; + +public: + weak_ptr() noexcept = default; + + weak_ptr(const shared_ptr& shared) noexcept + : ptr(shared.ptr), control_block(shared.control_block) { + if (control_block) { + ++control_block->weak_count; + } + } + + weak_ptr(const weak_ptr& other) noexcept + : ptr(other.ptr), control_block(other.control_block) { + if (control_block) { + ++control_block->weak_count; + } + } + + weak_ptr(weak_ptr&& other) noexcept + : ptr(other.ptr), control_block(other.control_block) { + other.ptr = nullptr; + other.control_block = nullptr; + } + + weak_ptr& operator=(const weak_ptr& other) noexcept { + if (this != &other) { + reset(); + ptr = other.ptr; + control_block = other.control_block; + if (control_block) { + ++control_block->weak_count; + } + } + return *this; + } + + weak_ptr& operator=(weak_ptr&& other) noexcept { + if (this != &other) { + reset(); + ptr = other.ptr; + control_block = other.control_block; + other.ptr = nullptr; + other.control_block = nullptr; + } + return *this; + } + + ~weak_ptr() { + reset(); + } + + void reset() noexcept { + if (control_block) { + if (--control_block->weak_count == 0 && control_block->shared_count == 0) { + delete control_block; + } + ptr = nullptr; + control_block = nullptr; + } + } + + size_t use_count() const noexcept { + return control_block ? control_block->shared_count.load() : 0; + } + + bool expired() const noexcept { + return use_count() == 0; + } + + shared_ptr lock() const noexcept { + if (expired()) { + return shared_ptr(); + } + + shared_ptr shared(ptr, control_block); + if (control_block) { + ++control_block->shared_count; + } + return shared; + } +}; + +} // namespace my \ No newline at end of file diff --git a/homework/shared_ptr/weak_ptr_tests.cpp b/homework/shared_ptr/weak_ptr_tests.cpp new file mode 100644 index 00000000..8c4bf994 --- /dev/null +++ b/homework/shared_ptr/weak_ptr_tests.cpp @@ -0,0 +1,66 @@ +#include +#include "weak_ptr.hpp" + +template class my::weak_ptr; + +TEST(WeakPtrTest, DefaultConstructor) { + my::weak_ptr wp; + EXPECT_TRUE(wp.expired()); + EXPECT_EQ(wp.use_count(), 0); +} + +TEST(WeakPtrTest, ConstructFromSharedPtr) { + my::shared_ptr sp(new int(42)); + my::weak_ptr wp(sp); + + EXPECT_FALSE(wp.expired()); + EXPECT_EQ(wp.use_count(), 1); +} + +TEST(WeakPtrTest, LockMethod) { + my::shared_ptr sp(new int(42)); + my::weak_ptr wp(sp); + + auto locked = wp.lock(); + EXPECT_TRUE(locked); + EXPECT_EQ(*locked, 42); + EXPECT_EQ(locked.use_count(), 2); +} + +TEST(WeakPtrTest, ExpiredCheck) { + my::weak_ptr wp; + { + my::shared_ptr sp(new int(42)); + wp = sp; + EXPECT_FALSE(wp.expired()); + } + EXPECT_TRUE(wp.expired()); +} + +TEST(WeakPtrTest, CopyConstructor) { + my::shared_ptr sp(new int(42)); + my::weak_ptr wp1(sp); + my::weak_ptr wp2(wp1); + + EXPECT_EQ(wp1.use_count(), 1); + EXPECT_EQ(wp2.use_count(), 1); +} + +TEST(WeakPtrTest, MoveConstructor) { + my::shared_ptr sp(new int(42)); + my::weak_ptr wp1(sp); + my::weak_ptr wp2(std::move(wp1)); + + EXPECT_TRUE(wp1.expired()); + EXPECT_FALSE(wp2.expired()); + EXPECT_EQ(wp2.use_count(), 1); +} + +TEST(WeakPtrTest, Reset) { + my::shared_ptr sp(new int(42)); + my::weak_ptr wp(sp); + + wp.reset(); + EXPECT_TRUE(wp.expired()); + EXPECT_EQ(wp.use_count(), 0); +} \ No newline at end of file