diff --git a/include/proxy/v4/detail/core.h b/include/proxy/v4/detail/core.h index 4262904..d19d243 100644 --- a/include/proxy/v4/detail/core.h +++ b/include/proxy/v4/detail/core.h @@ -617,10 +617,7 @@ struct copy_dispatch { } }; struct destroy_dispatch { - template - PRO4D_STATIC_CALL(void, T& self) noexcept(std::is_nothrow_destructible_v) { - std::destroy_at(&self); - } + PRO4D_STATIC_CALL(void, auto&&) noexcept {} }; template struct lifetime_meta_traits : std::type_identity {}; @@ -925,8 +922,8 @@ struct facade_traits : specialization_t, lifetime_meta_t, - lifetime_meta_t, + lifetime_meta_t, typename facade_traits::conv_meta, typename facade_traits::refl_meta>>; using indirect_accessor = composite_t< @@ -1331,8 +1328,8 @@ class proxy : public detail::facade_traits::direct_accessor, if constexpr (F::destructibility != constraint_level::trivial) { if (meta_.has_value()) { invoke(*this); + void() && noexcept(F::destructibility == + constraint_level::nothrow)>(std::move(*this)); } } } diff --git a/tests/proxy_lifetime_tests.cpp b/tests/proxy_lifetime_tests.cpp index 701e2ce..9e2967f 100644 --- a/tests/proxy_lifetime_tests.cpp +++ b/tests/proxy_lifetime_tests.cpp @@ -26,6 +26,14 @@ struct TestTrivialFacade ::support_destruction // ::build {}; +struct TestThrowingDestructionFacade + : pro::facade_builder // + ::add_convention // + ::support_copy // + ::support_relocation // + ::support_destruction // + ::build {}; + struct TestRttiFacade : pro::facade_builder // ::add_direct_reflection // ::add_facade_with_substitution // @@ -274,6 +282,22 @@ TEST(ProxyLifetimeTests, TestMoveConstrction_FromNull) { ASSERT_FALSE(p2.has_value()); } +TEST(ProxyLifetimeTests, TestDestruction_Exception) { + utils::LifetimeTracker tracker; + std::vector expected_ops; + auto destroy = [&] { + pro::proxy p{ + std::in_place_type, + &tracker}; + }; + tracker.ThrowOnNextDestruction(); + ASSERT_THROW(destroy(), utils::DestructionFailure); + expected_ops.emplace_back(1, + utils::LifetimeOperationType::kValueConstruction); + expected_ops.emplace_back(1, utils::LifetimeOperationType::kDestruction); + ASSERT_TRUE(tracker.GetOperations() == expected_ops); +} + TEST(ProxyLifetimeTests, TestNullAssignment_FromNullptr_ToValue) { utils::LifetimeTracker tracker; std::vector expected_ops; @@ -358,6 +382,31 @@ TEST(ProxyLifetimeTests, TestPolyAssignment_ToValue_Exception) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } +TEST(ProxyLifetimeTests, TestPolyAssignment_ToValue_DestructionException) { + utils::LifetimeTracker tracker; + std::vector expected_ops; + { + pro::proxy p{ + std::in_place_type, + &tracker}; + expected_ops.emplace_back(1, + utils::LifetimeOperationType::kValueConstruction); + utils::LifetimeTracker::Session session{&tracker}; + expected_ops.emplace_back(2, + utils::LifetimeOperationType::kValueConstruction); + tracker.ThrowOnNextDestruction(); + ASSERT_THROW(p = session, utils::DestructionFailure); + ASSERT_FALSE(p.has_value()); + expected_ops.emplace_back(3, + utils::LifetimeOperationType::kCopyConstruction); + expected_ops.emplace_back(1, utils::LifetimeOperationType::kDestruction); + expected_ops.emplace_back(3, utils::LifetimeOperationType::kDestruction); + ASSERT_TRUE(tracker.GetOperations() == expected_ops); + } + expected_ops.emplace_back(2, utils::LifetimeOperationType::kDestruction); + ASSERT_TRUE(tracker.GetOperations() == expected_ops); +} + TEST(ProxyLifetimeTests, TestPolyAssignment_FromValue_ToNull) { utils::LifetimeTracker tracker; std::vector expected_ops; @@ -630,6 +679,35 @@ TEST(ProxyLifetimeTests, TestCopyAssignment_FromValue_ToValue_Exception) { ASSERT_TRUE(tracker.GetOperations() == expected_ops); } +TEST(ProxyLifetimeTests, + TestCopyAssignment_FromValue_ToValue_DestructionException) { + utils::LifetimeTracker tracker; + std::vector expected_ops; + { + pro::proxy p1{ + std::in_place_type, + &tracker}; + expected_ops.emplace_back(1, + utils::LifetimeOperationType::kValueConstruction); + pro::proxy p2{ + std::in_place_type, &tracker}; + expected_ops.emplace_back(2, + utils::LifetimeOperationType::kValueConstruction); + tracker.ThrowOnNextDestruction(); + ASSERT_THROW(p1 = p2, utils::DestructionFailure); + ASSERT_FALSE(p1.has_value()); + ASSERT_TRUE(p2.has_value()); + ASSERT_EQ(ToString(*p2), "Session 2"); + expected_ops.emplace_back(3, + utils::LifetimeOperationType::kCopyConstruction); + expected_ops.emplace_back(1, utils::LifetimeOperationType::kDestruction); + expected_ops.emplace_back(3, utils::LifetimeOperationType::kDestruction); + ASSERT_TRUE(tracker.GetOperations() == expected_ops); + } + expected_ops.emplace_back(2, utils::LifetimeOperationType::kDestruction); + ASSERT_TRUE(tracker.GetOperations() == expected_ops); +} + TEST(ProxyLifetimeTests, TestCopyAssignment_FromValue_ToSelf) { utils::LifetimeTracker tracker; std::vector expected_ops; diff --git a/tests/utils.h b/tests/utils.h index ab53e63..b496ade 100644 --- a/tests/utils.h +++ b/tests/utils.h @@ -38,6 +38,8 @@ struct ConstructionFailure : std::exception { LifetimeOperationType type_; }; +struct DestructionFailure : std::exception {}; + class LifetimeTracker { public: LifetimeTracker() = default; @@ -69,13 +71,25 @@ class LifetimeTracker { return "Session " + std::to_string(self.id_); } - private: + protected: int id_; LifetimeTracker* const host_; }; + class ThrowingDestructionSession : public Session { + public: + using Session::Session; + ~ThrowingDestructionSession() noexcept(false) { + if (host_->throw_on_next_destruction_) { + host_->throw_on_next_destruction_ = false; + throw DestructionFailure{}; + } + } + }; + const std::vector& GetOperations() const { return ops_; } void ThrowOnNextConstruction() { throw_on_next_construction_ = true; } + void ThrowOnNextDestruction() { throw_on_next_destruction_ = true; } private: int AllocateId(LifetimeOperationType operation_type) { @@ -89,6 +103,7 @@ class LifetimeTracker { int max_id_ = 0; bool throw_on_next_construction_ = false; + bool throw_on_next_destruction_ = false; std::vector ops_; };