diff --git a/cpp/Platform.Delegates.Tests/AllTests.cpp b/cpp/Platform.Delegates.Tests/AllTests.cpp index b706d35..d728113 100644 --- a/cpp/Platform.Delegates.Tests/AllTests.cpp +++ b/cpp/Platform.Delegates.Tests/AllTests.cpp @@ -1,2 +1,3 @@ #include "DelegatesTest.cpp" -#include "MulticastDelegatesTest.cpp" \ No newline at end of file +#include "MulticastDelegatesTest.cpp" +#include "NonMutableDelegateTest.cpp" \ No newline at end of file diff --git a/cpp/Platform.Delegates.Tests/NonMutableDelegateTest.cpp b/cpp/Platform.Delegates.Tests/NonMutableDelegateTest.cpp new file mode 100644 index 0000000..b1316fe --- /dev/null +++ b/cpp/Platform.Delegates.Tests/NonMutableDelegateTest.cpp @@ -0,0 +1,200 @@ +#include +#include + +using namespace Platform::Delegates; + +namespace Platform::Delegates::Tests::NonMutableDelegateTest +{ + int globalValue = 0; + + void setGlobalValue(int value) { + globalValue = value; + } + + void setGlobalValue2(int value) { + globalValue = value + 10; + } + + class TestClass { + public: + int value = 0; + + void setValue(int v) { + value = v; + } + + int getValue() const { + return value; + } + + void incrementValue(int increment) { + value += increment; + } + }; + + // Test Case #1: Create from standard delegate + TEST(NonMutableDelegateTest, CreateFromStandardDelegate) + { + auto testObj = std::make_shared(); + Delegate standardDelegate = {testObj, &TestClass::setValue}; + + NonMutableDelegate nonMutableDelegate = standardDelegate; + nonMutableDelegate(42); + + ASSERT_EQ(testObj->value, 42); + } + + // Test Case #2: Create from shared_ptr and method pointer + TEST(NonMutableDelegateTest, CreateFromSharedPtrAndMethod) + { + auto testObj = std::make_shared(); + NonMutableDelegate delegate = {testObj, &TestClass::setValue}; + delegate(25); + + ASSERT_EQ(testObj->value, 25); + } + + // Test Case #3: Create from object reference and method pointer + TEST(NonMutableDelegateTest, CreateFromObjectReferenceAndMethod) + { + TestClass testObj; + NonMutableDelegate delegate = {testObj, &TestClass::setValue}; + delegate(15); + + ASSERT_EQ(testObj.value, 15); + } + + // Test Case #4: Create from unique_ptr and method pointer + TEST(NonMutableDelegateTest, CreateFromUniquePtrAndMethod) + { + auto testObj = std::make_unique(); + NonMutableDelegate delegate = {testObj, &TestClass::setValue}; + delegate(30); + + ASSERT_EQ(testObj->value, 30); + } + + // Test simple function pointer + TEST(NonMutableDelegateTest, SimpleFunctionTest) + { + NonMutableDelegate delegate = setGlobalValue; + delegate(100); + + ASSERT_EQ(globalValue, 100); + } + + // Test return values work correctly + TEST(NonMutableDelegateTest, ReturnValueTest) + { + TestClass testObj; + testObj.value = 99; + + NonMutableDelegate delegate = {testObj, &TestClass::getValue}; + int result = delegate(); + + ASSERT_EQ(result, 99); + } + + // Test with parameters and return value + TEST(NonMutableDelegateTest, ParametersAndReturnValueTest) + { + auto testObj = std::make_unique(); + testObj->value = 10; + + NonMutableDelegate setDelegate = {testObj, &TestClass::incrementValue}; + NonMutableDelegate getDelegate = {testObj, &TestClass::getValue}; + + setDelegate(5); // increment by 5 + int result = getDelegate(); + + ASSERT_EQ(result, 15); // 10 + 5 = 15 + } + + // Test equality operator + TEST(NonMutableDelegateTest, EqualityOperatorTest) + { + NonMutableDelegate delegate1 = setGlobalValue; + NonMutableDelegate delegate2 = delegate1; + + ASSERT_TRUE(delegate1 == delegate2); + } + + // Test inequality operator + TEST(NonMutableDelegateTest, InequalityOperatorTest) + { + NonMutableDelegate delegate1 = setGlobalValue; + NonMutableDelegate delegate2 = setGlobalValue2; + + ASSERT_FALSE(delegate1 == delegate2); + } + + // Test assignment operator + TEST(NonMutableDelegateTest, AssignmentOperatorTest) + { + NonMutableDelegate delegate1 = setGlobalValue; + NonMutableDelegate delegate2; + delegate2 = delegate1; + delegate2(55); + + ASSERT_EQ(globalValue, 55); + } + + // Test move assignment operator + TEST(NonMutableDelegateTest, MoveAssignmentOperatorTest) + { + NonMutableDelegate delegate1 = setGlobalValue; + NonMutableDelegate delegate2 = std::move(delegate1); + delegate2(77); + + ASSERT_EQ(globalValue, 77); + } + + // Test bad function call exception + TEST(NonMutableDelegateTest, BadFunctionCallTest) + { + NonMutableDelegate delegate; + + ASSERT_THROW(delegate(), std::bad_function_call); + } + + // Test member function equality with same object + TEST(NonMutableDelegateTest, MemberFunctionEqualityTest) + { + TestClass testObj; + NonMutableDelegate delegate1 = {testObj, &TestClass::setValue}; + NonMutableDelegate delegate2 = {testObj, &TestClass::setValue}; + + ASSERT_TRUE(delegate1 == delegate2); + } + + // Test member function inequality with different objects + TEST(NonMutableDelegateTest, MemberFunctionInequalityTest) + { + TestClass testObj1; + TestClass testObj2; + NonMutableDelegate delegate1 = {testObj1, &TestClass::setValue}; + NonMutableDelegate delegate2 = {testObj2, &TestClass::setValue}; + + ASSERT_FALSE(delegate1 == delegate2); + } + + // Test the key benefit: no shared_ptr overhead for scoped objects + TEST(NonMutableDelegateTest, ScopedObjectLifetimeTest) + { + // This test demonstrates the main purpose - using objects with guaranteed lifetime + { + TestClass scopedObj; + NonMutableDelegate delegate = {scopedObj, &TestClass::setValue}; + + // The delegate uses raw pointer, no shared_ptr overhead + delegate(123); + ASSERT_EQ(scopedObj.value, 123); + + // As long as scopedObj is in scope, the delegate is safe to use + delegate(456); + ASSERT_EQ(scopedObj.value, 456); + } + // After this scope, delegate would be invalid, but that's the user's responsibility + // This is the trade-off for avoiding shared_ptr overhead + } +} \ No newline at end of file diff --git a/cpp/Platform.Delegates/Platform.Delegates.NonMutableDelegate.h b/cpp/Platform.Delegates/Platform.Delegates.NonMutableDelegate.h new file mode 100644 index 0000000..69288eb --- /dev/null +++ b/cpp/Platform.Delegates/Platform.Delegates.NonMutableDelegate.h @@ -0,0 +1,212 @@ +#pragma once + +#include "Platform.Delegates.Delegate.h" +#include +#include +#include + +namespace Platform::Delegates +{ + template + class NonMutableDelegate; + + template + class NonMutableDelegate + { + public: + using DelegateRawFunctionType = ReturnType(Args...); + + constexpr NonMutableDelegate() noexcept = default; + + NonMutableDelegate(const NonMutableDelegate&) noexcept = default; + + NonMutableDelegate(NonMutableDelegate&&) noexcept = default; + + // Case #1: Create from standard delegate + NonMutableDelegate(const Delegate& delegate) + : sourceDelegate(delegate) { } + + // Case #2: Create from ref to object and ptr to method (shared_ptr) + template + NonMutableDelegate(const std::shared_ptr& object, ReturnType(Class::*member)(Args...)) + : memberMethod(std::make_shared>(object.get(), member)) { } + + template + NonMutableDelegate(const std::shared_ptr& object, ReturnType(Class::*member)(Args...) const) + : memberMethod(std::make_shared>(object.get(), member)) { } + + // Case #3: Create from ref to object and ptr to method (raw reference) + template + NonMutableDelegate(Class& object, ReturnType(Class::*member)(Args...)) + : memberMethod(std::make_shared>(&object, member)) { } + + template + NonMutableDelegate(Class& object, ReturnType(Class::*member)(Args...) const) + : memberMethod(std::make_shared>(&object, member)) { } + + // Case #4: Create from unique_ptr to object and ptr to method + template + NonMutableDelegate(const std::unique_ptr& object, ReturnType(Class::*member)(Args...)) + : memberMethod(std::make_shared>(object.get(), member)) { } + + template + NonMutableDelegate(const std::unique_ptr& object, ReturnType(Class::*member)(Args...) const) + : memberMethod(std::make_shared>(object.get(), member)) { } + + // Simple function pointer constructor + constexpr NonMutableDelegate(DelegateRawFunctionType simpleFunction) noexcept + : simpleFunction(simpleFunction) { } + + virtual ~NonMutableDelegate() = default; + + NonMutableDelegate& operator=(const NonMutableDelegate& other) noexcept + { + if (this != &other) + { + this->simpleFunction = other.simpleFunction; + this->memberMethod = other.memberMethod; + this->sourceDelegate = other.sourceDelegate; + } + return *this; + } + + NonMutableDelegate& operator=(NonMutableDelegate&& other) noexcept + { + if (this != &other) + { + this->simpleFunction = std::move(other.simpleFunction); + this->memberMethod = std::move(other.memberMethod); + this->sourceDelegate = std::move(other.sourceDelegate); + } + return *this; + } + + virtual ReturnType operator()(Args... args) + { + if (simpleFunction) + { + return simpleFunction(std::forward(args)...); + } + if (memberMethod) + { + return (*memberMethod)(std::forward(args)...); + } + if (sourceDelegate) + { + return sourceDelegate.value()(std::forward(args)...); + } + throw std::bad_function_call{}; + } + + virtual bool operator==(const NonMutableDelegate& other) const + { + if (simpleFunction && other.simpleFunction) + { + return simpleFunction == other.simpleFunction; + } + if (memberMethod && other.memberMethod) + { + return *memberMethod == *other.memberMethod; + } + if (sourceDelegate && other.sourceDelegate) + { + return sourceDelegate.value() == other.sourceDelegate.value(); + } + return false; + } + + private: + class MemberMethodWrapperBase + { + public: + virtual ReturnType operator()(Args... args) = 0; + virtual bool operator==(const MemberMethodWrapperBase& other) const = 0; + virtual ~MemberMethodWrapperBase() = default; + }; + + template + class MemberMethodWrapper : public MemberMethodWrapperBase + { + public: + MemberMethodWrapper(Class* object, ReturnType(Class::*method)(Args...)) noexcept + : objectPtr(object), method(method) { } + + ReturnType operator()(Args... args) override + { + return (objectPtr->*method)(args...); + } + + bool operator==(const MemberMethodWrapperBase& other) const override + { + const MemberMethodWrapper* otherWrapper = dynamic_cast(&other); + if (!otherWrapper) + { + return false; + } + return this->objectPtr == otherWrapper->objectPtr + && this->method == otherWrapper->method; + } + + private: + Class* objectPtr; + ReturnType(Class::*method)(Args...); + }; + + template + class ConstMemberMethodWrapper : public MemberMethodWrapperBase + { + public: + ConstMemberMethodWrapper(Class* object, ReturnType(Class::*method)(Args...) const) noexcept + : objectPtr(object), method(method) { } + + ReturnType operator()(Args... args) override + { + return (objectPtr->*method)(args...); + } + + bool operator==(const MemberMethodWrapperBase& other) const override + { + const ConstMemberMethodWrapper* otherWrapper = dynamic_cast(&other); + if (!otherWrapper) + { + return false; + } + return this->objectPtr == otherWrapper->objectPtr + && this->method == otherWrapper->method; + } + + private: + Class* objectPtr; + ReturnType(Class::*method)(Args...) const; + }; + + DelegateRawFunctionType* simpleFunction = nullptr; + std::shared_ptr memberMethod = nullptr; + std::optional> sourceDelegate; + }; + + // Deduction guides + template + NonMutableDelegate(ReturnType(function)(Args...)) -> NonMutableDelegate; + + template + NonMutableDelegate(const Delegate& delegate) -> NonMutableDelegate; + + template + NonMutableDelegate(Class& object, ReturnType(Class::*member)(Args...)) -> NonMutableDelegate; + + template + NonMutableDelegate(Class& object, ReturnType(Class::*member)(Args...) const) -> NonMutableDelegate; + + template + NonMutableDelegate(const std::shared_ptr& object, ReturnType(Class::*member)(Args...)) -> NonMutableDelegate; + + template + NonMutableDelegate(const std::shared_ptr& object, ReturnType(Class::*member)(Args...) const) -> NonMutableDelegate; + + template + NonMutableDelegate(const std::unique_ptr& object, ReturnType(Class::*member)(Args...)) -> NonMutableDelegate; + + template + NonMutableDelegate(const std::unique_ptr& object, ReturnType(Class::*member)(Args...) const) -> NonMutableDelegate; +} \ No newline at end of file diff --git a/cpp/Platform.Delegates/Platform.Delegates.h b/cpp/Platform.Delegates/Platform.Delegates.h index c40749d..3c9fdb3 100644 --- a/cpp/Platform.Delegates/Platform.Delegates.h +++ b/cpp/Platform.Delegates/Platform.Delegates.h @@ -2,3 +2,4 @@ #include "Platform.Delegates.Delegate.h" #include "Platform.Delegates.MulticastDelegate.h" +#include "Platform.Delegates.NonMutableDelegate.h" diff --git a/examples/test_nonmutable_delegate b/examples/test_nonmutable_delegate new file mode 100755 index 0000000..46d6231 Binary files /dev/null and b/examples/test_nonmutable_delegate differ diff --git a/examples/test_nonmutable_delegate.cpp b/examples/test_nonmutable_delegate.cpp new file mode 100644 index 0000000..4133e00 --- /dev/null +++ b/examples/test_nonmutable_delegate.cpp @@ -0,0 +1,144 @@ +#include +#include +#include "../cpp/Platform.Delegates/Platform.Delegates.h" + +using namespace Platform::Delegates; + +int globalValue = 0; + +void setGlobalValue(int value) { + globalValue = value; +} + +class TestClass { +public: + int value = 0; + + void setValue(int v) { + value = v; + } + + int getValue() const { + return value; + } + + void incrementValue(int increment) { + value += increment; + } +}; + +void testCase1_CreateFromStandardDelegate() { + std::cout << "=== Test Case 1: Create from standard delegate ===" << std::endl; + + auto testObj = std::make_shared(); + Delegate standardDelegate = {testObj, &TestClass::setValue}; + + NonMutableDelegate nonMutableDelegate = standardDelegate; + nonMutableDelegate(42); + + std::cout << "Expected: 42, Got: " << testObj->value << std::endl; + std::cout << (testObj->value == 42 ? "PASS" : "FAIL") << std::endl << std::endl; +} + +void testCase2_CreateFromSharedPtr() { + std::cout << "=== Test Case 2: Create from shared_ptr and method ===" << std::endl; + + auto testObj = std::make_shared(); + NonMutableDelegate delegate = {testObj, &TestClass::setValue}; + delegate(25); + + std::cout << "Expected: 25, Got: " << testObj->value << std::endl; + std::cout << (testObj->value == 25 ? "PASS" : "FAIL") << std::endl << std::endl; +} + +void testCase3_CreateFromObjectReference() { + std::cout << "=== Test Case 3: Create from object reference and method ===" << std::endl; + + TestClass testObj; + NonMutableDelegate delegate = {testObj, &TestClass::setValue}; + delegate(15); + + std::cout << "Expected: 15, Got: " << testObj.value << std::endl; + std::cout << (testObj.value == 15 ? "PASS" : "FAIL") << std::endl << std::endl; +} + +void testCase4_CreateFromUniquePtr() { + std::cout << "=== Test Case 4: Create from unique_ptr and method ===" << std::endl; + + auto testObj = std::make_unique(); + NonMutableDelegate delegate = {testObj, &TestClass::setValue}; + delegate(30); + + std::cout << "Expected: 30, Got: " << testObj->value << std::endl; + std::cout << (testObj->value == 30 ? "PASS" : "FAIL") << std::endl << std::endl; +} + +void testSimpleFunction() { + std::cout << "=== Test: Simple function pointer ===" << std::endl; + + NonMutableDelegate delegate = setGlobalValue; + delegate(100); + + std::cout << "Expected: 100, Got: " << globalValue << std::endl; + std::cout << (globalValue == 100 ? "PASS" : "FAIL") << std::endl << std::endl; +} + +void testReturnValue() { + std::cout << "=== Test: Return values ===" << std::endl; + + TestClass testObj; + testObj.value = 99; + + NonMutableDelegate delegate = {testObj, &TestClass::getValue}; + int result = delegate(); + + std::cout << "Expected: 99, Got: " << result << std::endl; + std::cout << (result == 99 ? "PASS" : "FAIL") << std::endl << std::endl; +} + +void demonstrateMainBenefit() { + std::cout << "=== Demonstration: Key benefit - no shared_ptr overhead ===" << std::endl; + + // This demonstrates the main purpose - using objects with guaranteed lifetime + { + TestClass scopedObj; + NonMutableDelegate delegate = {scopedObj, &TestClass::setValue}; + + // The delegate uses raw pointer, no shared_ptr overhead + delegate(123); + std::cout << "First call - Expected: 123, Got: " << scopedObj.value << std::endl; + std::cout << (scopedObj.value == 123 ? "PASS" : "FAIL") << std::endl; + + // As long as scopedObj is in scope, the delegate is safe to use + delegate(456); + std::cout << "Second call - Expected: 456, Got: " << scopedObj.value << std::endl; + std::cout << (scopedObj.value == 456 ? "PASS" : "FAIL") << std::endl; + } + // After this scope, delegate would be invalid, but that's the user's responsibility + // This is the trade-off for avoiding shared_ptr overhead + + std::cout << std::endl; +} + +int main() { + std::cout << "Testing NonMutableDelegate Implementation" << std::endl; + std::cout << "=========================================" << std::endl << std::endl; + + try { + testCase1_CreateFromStandardDelegate(); + testCase2_CreateFromSharedPtr(); + testCase3_CreateFromObjectReference(); + testCase4_CreateFromUniquePtr(); + testSimpleFunction(); + testReturnValue(); + demonstrateMainBenefit(); + + std::cout << "All tests completed!" << std::endl; + + } catch (const std::exception& e) { + std::cout << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +} \ No newline at end of file