-
Notifications
You must be signed in to change notification settings - Fork 3
/
concurrent.hpp
366 lines (324 loc) · 14.3 KB
/
concurrent.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
/**
* ______________________________________________________
* Header-only concurrency wrapper.
*
* @file concurrent.hpp
* @author Mustafa Kemal GILOR <[email protected]>
* @date 02.12.2020
*
* SPDX-License-Identifier: MIT
* ______________________________________________________
*/
#pragma once
#include <memory>
#include <chrono>
#include <type_traits>
namespace mkg {
/**
* Checks whether given type T satisfies the requirements of
* `BasicLockable` concept (a.k.a named requirement).
*
* @see http://www.cplusplus.com/reference/concept/BasicLockable/
*
* @tparam T Type to check
*/
template<typename T>
concept basic_lockable = requires(T a) {
/**
* @brief Instantiation of type T must have a public function named `lock`
* and it must have return type of `void`.
*/
{ a.lock() } -> std::same_as<void>;
/**
* @brief Instantiation of type T must have a public function named `unlock`
* and it must have return type of `void`, and it must be noexcept.
*/
{ a.unlock() } -> std::same_as<void>;
};
/**
* Checks whether given type T satisfies the requirements of
* `Lockable` concept (a.k.a named requirement).
*
* @see http://www.cplusplus.com/reference/concept/Lockable/
*
* @tparam T Type to check
*/
template<typename T>
concept lockable = basic_lockable<T> && requires (T a){
/**
* @brief Instantiation of type T must have a public function named `try_lock`
* and it must have return type of `bool`.
*/
{ a.try_lock() } -> std::same_as<bool>;
};
/**
* Checks whether given type T satisfies the requirements of
* `TimedLockable` concept (a.k.a named requirement).
*
* @see http://www.cplusplus.com/reference/concept/TimedLockable/
*
* @tparam T Type to check
*/
template<typename T>
concept timed_lockable = lockable<T> && requires (T a) {
/**
* @brief Instantiation of type T must have a public function named `try_lock_for`
* which accepts std::chrono::duration<> as its argumentand it must have return type
* of `bool`.
*/
{ a.try_lock_for(std::chrono::seconds{}) } -> std::same_as<bool>;
/**
* @brief Instantiation of type T must have a public function named `try_lock_until`
* which accepts std::chrono::time_point<> as its argumentand it must have return type
* of `bool`.
*/
{ a.try_lock_until(std::chrono::time_point<std::chrono::steady_clock>{ std::chrono::seconds{} }) } -> std::same_as<bool>;
};
/**
* Checks whether given type T satisfies the requirements of
* `basic_shared_lockable` concept (a.k.a named requirement).
*
* ** There are no official named requirements for this **.
*
* @tparam T Type to check
*/
template<typename T>
concept basic_shared_lockable = basic_lockable<T> && requires (T a) {
/**
* @brief Instantiation of type T must have a public function named `lock_shared`
* it must have return type of `void`.
*/
{ a.lock_shared() } -> std::same_as<void>;
/**
* @brief Instantiation of type T must have a public function named `unlock_shared`
* and it must have return type of `void`, and it must be noexcept.
*/
{ a.unlock_shared() } -> std::same_as<void>;
};
/**
* Checks whether given type T satisfies the requirements of
* `shared_lockable` concept (a.k.a named requirement).
*
* ** There are no official named requirements for this **.
*
* @tparam T Type to check
*/
template<typename T>
concept shared_lockable = basic_shared_lockable<T> && requires (T a) {
/**
* @brief Instantiation of type T must have a public function named `try_lock_shared`
* and it must have return type of `bool`.
*/
{ a.try_lock_shared() } -> std::same_as<bool>;
};
/**
* Checks whether given type T satisfies the requirements of
* `shared_timed_lockable` concept (a.k.a named requirement).
*
* ** There are no official named requirements for this **.
*
* @tparam T Type to check
*/
template<typename T>
concept shared_timed_lockable = shared_lockable<T> && requires (T a) {
/**
* @brief Instantiation of type T must have a public function named `try_lock_shared_for`
* which accepts std::chrono::duration<> as its argumentand it must have return type
* of `bool`.
*/
{ a.try_lock_shared_for(std::chrono::seconds{}) } -> std::same_as<bool>;
/**
* @brief Instantiation of type T must have a public function named `try_lock_shared_until`
* which accepts std::chrono::time_point<> as its argumentand it must have return type
* of `bool`.
*/
{ a.try_lock_shared_until(std::chrono::time_point<std::chrono::steady_clock>{ std::chrono::seconds{} }) } -> std::same_as<bool>;
};
/**
* A base class to make derived class non-copyable.
*
* Inherit from this class to in order to make derived class noncopyable.
* By non-copyable, it means the following operations are explicitly removed from the derived;
*
* Copy-construction from mutable lvalue reference
* Copy-construction from immutable lvalue reference
* Copy-assignment from mutable lvalue reference
* Copy-assignment from immutable lvalue reference
*/
class noncopyable{
noncopyable(noncopyable&) = delete;
noncopyable(const noncopyable&) = delete;
noncopyable& operator=(noncopyable&) = delete;
noncopyable& operator=(const noncopyable&) = delete;
public:
noncopyable() = default;
~noncopyable() = default;
noncopyable(noncopyable&&) = default;
noncopyable& operator=(noncopyable&&) = default;
};
/**
* A RAII wrapper type which wraps a resource and its' associated lock. The wrapper locks given `Lockable` object
* via instantiating a `LockType` object upon construction. This `LockType` object and the resource will be held
* as a class member together. This will guarantee that acquired type of lock will not be released until the wrapper
* object's destructor is called.
*
* The wrapper has "*" and "->" operator overloads to provide access wrapped resource. If wrapped resource
* type is a class type, "->" operator will allow client to access its` members directly. The (*) operator
* will return cv-qualified reference to the wrapped object.
*
* @tparam TypeQualifiedNonConcurrentType CV qualified type of the wrapped resource. This will determine the access level.
* @tparam LockableType Type of the `Lockable` object
* @tparam LockType Type of the `Lock` object to lock the `Lockable` type
*/
template<typename TypeQualifiedNonConcurrentType, typename LockableType, template <typename...> typename LockType>
requires (std::is_reference_v<TypeQualifiedNonConcurrentType>)
class accessor : private noncopyable {
public:
/**
* @brief Accessor object constructor
*
* @param resource Resource to grant desired access to
* @param lockable Lockable, which will be locked during the access
*/
constexpr explicit accessor(LockableType& lockable, TypeQualifiedNonConcurrentType resource)
requires (!std::is_const_v<LockableType>)
: lock(lockable), locked_resource(resource)
{}
/**
* @brief Accessor object constructor
*
* @param resource Resource to grant desired access to
* @param lock Existing lock, which will be moved into the accessor
*/
constexpr explicit accessor(LockType<LockableType> && lock, TypeQualifiedNonConcurrentType resource)
requires (std::is_rvalue_reference_v<decltype(lock)>)
: lock(std::forward<LockType<LockableType>>(lock)), locked_resource(resource)
{}
/**
* @brief Class member access operator overload to behave as if
* instance of `accessor` class is a `locked_resource` pointer.
*
* @note This operator has a special condition.
*
* Quoting from C++98 standard §13.5.6/1 "Class member access":
*
* "An expression x->m is interpreted as (x.operator->())->m for a class
* object x of type T if T::operator-> exists and if the operator is selected
* at the best match function by the overload resolution mechanism (13.3)."
*
* which basically means when we call this operator, we will access member functions
* of `TypeQualifiedNonConcurrentType`.
*
* @return Provides member access if `TypeQualifiedNonConcurrentType` is a class type. Otherwise, it has no use.
*/
inline decltype(auto) operator->() noexcept {
struct member_access_operator_decorator {
constexpr explicit member_access_operator_decorator(TypeQualifiedNonConcurrentType value)
: value(value)
{}
constexpr decltype(auto) operator->() const noexcept { return &value; }
private:
TypeQualifiedNonConcurrentType value;
};
return member_access_operator_decorator { locked_resource };
}
/**
* Dereference (star) operator overload
*
* @return cv-qualified reference to the resource
*/
inline decltype(auto) operator*() noexcept { return locked_resource; }
private:
LockType<LockableType> lock;
TypeQualifiedNonConcurrentType locked_resource;
};
template<typename NonConcurrentType, basic_shared_lockable LockableType, template <typename...> typename LockType>
using shared_accessor = accessor<std::add_lvalue_reference_t<std::add_const_t<NonConcurrentType>>, LockableType, LockType>;
template<typename NonConcurrentType, basic_lockable LockableType, template <typename...> typename LockType>
using exclusive_accessor = accessor<std::add_lvalue_reference_t<NonConcurrentType>, LockableType, LockType>;
/**
* A wrapper type to protect a `NonConcurrentType` via locking a `LockableType`
* via;
*
* - `SharedLockType` when read only access
* - `ExclusiveLockType` when read & write access
*
* is requested.
*
* @tparam NonConcurrentType A non thread-safe type to wrap
* @tparam LockableType A type which satisfies the `BasicSharedLockable` named requirement (eg. std::shared_mutex)
* @tparam SharedLockType A RAII type which will be used to lock the `Lockable` when read access is requested (eg. std::shared_lock)
* @tparam ExclusiveLockType A RAII type which will be used to lock the `Lockable` when write access is requested (eg. std::unique_lock)
*/
template<typename NonConcurrentType, basic_shared_lockable LockableType,
template <typename...> typename SharedLockType,
template <typename...> typename ExclusiveLockType >
class concurrent {
public:
using shared_accessor_t = shared_accessor<NonConcurrentType, LockableType, SharedLockType>;
using exclusive_accessor_t = exclusive_accessor<NonConcurrentType, LockableType, ExclusiveLockType>;
/**
* @brief Default constructor
*
* Instantiates the `NonConcurrentType` by invoking its' default constructor.
*
* @exception noexcept if `NonConcurrentType` is `NoThrowDefaultConstructible`
*/
concurrent()
noexcept(std::is_nothrow_default_constructible_v<NonConcurrentType>)
requires (std::is_default_constructible_v<NonConcurrentType>)
: resource{}
{}
/**
* @brief Copy constructor (from wrapped type)
*
* Instantiates the `NonConcurrentType` by invoking its' copy constructor.
*
* @exception noexcept if `NonConcurrentType` is `NoThrowCopyConstructible`
*/
explicit concurrent(const NonConcurrentType& value)
noexcept(std::is_nothrow_copy_constructible_v<NonConcurrentType>)
requires (std::is_copy_constructible_v<NonConcurrentType>)
: resource(value)
{}
/**
* @brief Move constructor (from wrapped type)
*
* Instantiates the `NonConcurrentType` by invoking its' move constructor.
*
* @exception noexcept if `NonConcurrentType` is `NoThrowMoveConstructible`
*/
explicit concurrent(NonConcurrentType&& value)
noexcept(std::is_nothrow_move_constructible_v<NonConcurrentType>)
requires (std::is_move_constructible_v<NonConcurrentType>)
: resource(std::move(value))
{}
/**
* @brief Destructor
*
* @exception noexcept if `NonConcurrentType` is `NoThrowDestructible`
*/
~concurrent() noexcept(std::is_nothrow_destructible_v<NonConcurrentType>) = default;
/**
* @brief Get read-only (shared) access to underlying wrapped object. Access object will guarantee
* `Lockable` object will be locked via `SharedLockType` from beginning to end of the
* returned accessor object.
*
* @return shared_accessor_t Read-only (shared) accessor object to the wrapped object
*/
decltype(auto) read_access_handle() const noexcept { return shared_accessor_t{ lockable, resource }; }
/**
* @brief Get write (exclusive) access to underlying wrapped object. Access object will guarantee
* `Lockable` object will be locked via `ExclusiveLockType` from beginning to end of the
* returned accessor object.
*
* @exception Exception-safe
*
* @return exclusive_accessor_t Read & write (exclusive) accessor object to the wrapped object
*/
decltype(auto) write_access_handle() noexcept { return exclusive_accessor_t{ lockable, resource }; }
private:
NonConcurrentType resource;
mutable LockableType lockable;
};
}