Skip to content

Commit d9b9e56

Browse files
committed
Core: Tighten CleanupType constraints
I needed to use this for another PR, and while it's completely functional as-is, I realized we can tighten up the constraints a bit on the type we accept for the cleanup function, and also make the move operation safer. Since the orginal was move enabled this seems like a good idea. Also added some means to cancel or execute early if needed. Also add unit tests Signed-off-by: Charlie Vigue <charlie.vigue@openvpn.com>
1 parent 8cd4103 commit d9b9e56

2 files changed

Lines changed: 363 additions & 15 deletions

File tree

openvpn/common/cleanup.hpp

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,115 @@
1313
#define OPENVPN_COMMON_CLEANUP_H
1414

1515
#include <utility>
16+
#include <optional>
17+
#include <concepts>
18+
#include <functional>
19+
#include <type_traits>
1620

1721
namespace openvpn {
1822

23+
/**
24+
* @brief A scope guard that runs a callable on destruction unless dismissed or released.
25+
* @tparam F The type of the callable to run on destruction.
26+
* @details
27+
* The Cleanup class template provides a mechanism to ensure that a specified callable
28+
* is executed when the Cleanup object goes out of scope, unless it has been dismissed
29+
* or released. This is useful for resource management and cleanup tasks that need to
30+
* be performed when exiting a scope.
31+
* @todo Keep std::scope_exit in mind when moving to C++23.
32+
*/
1933
template <typename F>
34+
requires std::invocable<F>
2035
class CleanupType
2136
{
2237
public:
23-
CleanupType(F method) noexcept
38+
explicit CleanupType(F method) noexcept(std::is_nothrow_move_constructible_v<F>)
2439
: clean(std::move(method))
2540
{
2641
}
42+
CleanupType(const CleanupType &) = delete;
43+
CleanupType &operator=(const CleanupType &) = delete;
44+
CleanupType(CleanupType &&other) noexcept
45+
: clean(std::exchange(other.clean, std::nullopt))
46+
{
47+
}
48+
CleanupType &operator=(CleanupType &&other) noexcept
49+
{
50+
if (this != &other)
51+
{
52+
clean = std::exchange(other.clean, std::nullopt);
53+
}
54+
return *this;
55+
}
2756

28-
CleanupType(CleanupType &&) = default;
57+
/**
58+
* @brief Destructor that executes the cleanup action if not dismissed or released.
59+
* @details When the CleanupType object is destroyed, if the cleanup action has not
60+
* been dismissed or released, it will be invoked. Any exceptions thrown by the
61+
* cleanup action are caught and swallowed to prevent exceptions from escaping the
62+
* destructor.
63+
*/
64+
~CleanupType() noexcept
65+
{
66+
if (clean)
67+
{
68+
try
69+
{
70+
std::invoke(*clean);
71+
}
72+
catch (...)
73+
{
74+
// swallow exceptions to avoid throwing from destructor
75+
}
76+
}
77+
}
2978

30-
~CleanupType()
79+
public:
80+
/**
81+
* @brief Dismiss the cleanup action, preventing it from being executed on destruction.
82+
* @details After calling dismiss, the cleanup action will not be invoked when the
83+
* CleanupType object is destroyed.
84+
*/
85+
void dismiss() noexcept
86+
{
87+
clean.reset();
88+
}
89+
/**
90+
* @brief Release the cleanup action, returning the callable and preventing it from being
91+
* executed on destruction.
92+
* @return An optional containing the callable if it was set, or std::nullopt if it was
93+
* dismissed or already released.
94+
* @details After calling release, the cleanup action will not be invoked when the
95+
* CleanupType object is destroyed, and the caller takes ownership of the callable. This
96+
* allows the caller to manage the callable's lifetime independently, including calling it
97+
* earlier or later than it might be otherwise called.
98+
*/
99+
std::optional<std::function<void()>> release() noexcept
31100
{
32-
clean();
101+
if (clean)
102+
{
103+
std::function<void()> func = std::move(*clean);
104+
clean.reset();
105+
return func;
106+
}
107+
return std::nullopt;
33108
}
34109

35110
private:
36-
CleanupType(const CleanupType &) = delete;
37-
CleanupType &operator=(const CleanupType &) = delete;
38-
39-
F clean;
111+
std::optional<F> clean;
40112
};
41113

114+
/**
115+
* @brief Factory function to create a CleanupType object.
116+
* @tparam F The type of the callable to run on destruction.
117+
* @param method The callable to be executed on destruction.
118+
* @return A CleanupType object that will execute the callable on destruction.
119+
* @details This function simplifies the creation of CleanupType objects by deducing
120+
the type of the callable and forwarding it to the CleanupType constructor.
121+
*/
42122
template <typename F>
43-
inline CleanupType<F> Cleanup(F method) noexcept
123+
requires std::invocable<F>
124+
inline CleanupType<F> Cleanup(F method) noexcept(std::is_nothrow_move_constructible_v<F>)
44125
{
45126
return CleanupType<F>(std::move(method));
46127
}

0 commit comments

Comments
 (0)