-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathunique_resource.qbk
631 lines (490 loc) · 33.3 KB
/
unique_resource.qbk
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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
[/
/ Copyright 2023-2024 Andrey Semashev
/
/ Distributed under the Boost Software License, Version 1.0.
/ (See accompanying file LICENSE_1_0.txt or copy at
/ https://www.boost.org/LICENSE_1_0.txt)
/
/ This document is a part of Boost.Scope library documentation.
/]
[section:unique_resource Unique resource wrapper]
#include <``[boost_scope_unique_resource_hpp]``>
Boost.Scope provides a [class_scope_unique_resource] class template. This is a wrapper for an arbitrary resource that represents exclusive
ownership of the resource. The wrapper offers access to the owned resource and automatically calls a deleter function object to free the
resource upon destruction. This is a generalization of `std::unique_ptr`, but while `std::unique_ptr` is only used to wrap pointers,
[class_scope_unique_resource] can wrap other types of resources as well.
A resource type must have the following properties to be compatible with [class_scope_unique_resource]:
* Move-constructible, where the move constructor is marked as `noexcept`, or
* Copy-constructible, or
* An lvalue reference to an object type.
Note that if the resource type is a reference, the referenced object must be stored externally to the [class_scope_unique_resource] wrapper
and must remain valid for the entire duration of ownership by the wrapper. Users are not expected to access the resource object other than
through the [class_scope_unique_resource] wrapper. For example, it is not allowed to modify the resource object by setting it to an invalid
state or explicitly freeing the resource circumventing the resource wrapper, as the wrapper will still invoke the deleter on the then-invalid
resource.
The deleter must be a function object type that is callable on an lvalue of the resource type. The deleter must be copy-constructible.
Although not strictly required, it is generally highly recommended that calling a deleter on a resource doesn't throw exceptions and is marked
`noexcept`, as the deleter will often be called from the [class_scope_unique_resource] destructor.
Like `std::unique_ptr`, [class_scope_unique_resource] is not copyable and supports a number of other operations, such as move construction and
assignment, `reset`, `release` and `swap` with the similar semantics. Some of the operations impose additional requirements on the resource and
deleter types; such operations will not be available if those requirements aren't met. For example, `swap` is only supported if both
the resource and deleter types are swappable, and at least one of those types is nothrow swappable (the last part is necessary to implement the
[@https://en.cppreference.com/w/cpp/language/exceptions strong exception guarantee] for the `swap` operation). The requirements are listed for
each operation in the [class_scope_unique_resource] class reference.
[tip If the resource type is dereferenceable (i.e. supports unary `operator*`), [class_scope_unique_resource] also provides `operator*` and
`operator->`, making it act more like a smart-pointer.]
Let's consider a usage example. Here, we need to compare the contents of two files; `equal_files` returns `true` if the files have equal
contents and `false` otherwise.
// A deleter for POSIX-like file descriptors
struct fd_deleter
{
void operator() (int fd) const noexcept
{
if (fd >= 0)
close(fd);
}
};
// Opens a file with the given filename for reading and returns wrapped file descriptor
boost::scope::unique_resource< int, fd_deleter > open_file(std::string const& filename)
{
boost::scope::unique_resource< int, fd_deleter > file(open(filename.c_str(), O_RDONLY));
if (file.get() < 0)
{
int err = errno;
throw std::system_error(err, std::generic_category(), "Failed to open file " + filename);
}
return file;
}
// Reads contents of a file denoted by fd into buffer and returns the number of read bytes
std::size_t read_file(int fd, unsigned char* buf, std::size_t size)
{
std::size_t total_read_size = 0;
while (total_read_size < size)
{
ssize_t read_size = read(fd, buf + total_read_size, size - total_read_size);
if (read_size < 0)
{
int err = errno;
if (err == EINTR)
continue;
throw std::system_error(err, std::generic_category(), "Failed to write data");
}
if (read_size == 0)
{
// End of file
break;
}
total_read_size += read_size;
}
return total_read_size;
}
// Compares contents of two files for equality
bool equal_files(std::string const& filename1, std::string const& filename2)
{
// Create unique resource wrappers for files
auto file1 = open_file(filename1.c_str());
auto file2 = open_file(filename2.c_str());
// Use them to read file contents
constexpr std::size_t buf_size = 1024;
unsigned char buf1[buf_size];
unsigned char buf2[buf_size];
while (true)
{
std::size_t size1 = read_file(file1.get(), buf1, buf_size);
std::size_t size2 = read_file(file2.get(), buf2, buf_size);
if (size1 != size2)
{
// File sizes are different
return false;
}
if (std::memcmp(buf1, buf2, size1) != 0)
{
// File contents are different
return false;
}
if (size1 < buf_size)
{
// Files have ended. At this point we know they have equal contents.
break;
}
}
return true;
}
[note POSIX-like file descriptors are supported on Windows through non-standard APIs like
[@https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/open-wopen `_open`],
[@https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/close `_close`],
[@https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/read `_read`],
[@https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/write `_write`] and others. It is often possible to port a program
performing simple IO with regular files and file descriptors to Windows by simple renaming. However, here and below we will be using
the standard POSIX nomenclature.]
In this example, there are several points to note. First, the `fd_deleter` function object checks if the file descriptor is valid before
passing it to `close`. This is necessary in this piece of code because we unconditionally initialize [class_scope_unique_resource] object
with the value returned by `open`, which may be -1 indicating an error. By default, [class_scope_unique_resource] doesn't discriminate
valid (i.e. allocated) resource values from invalid (i.e. unallocated), which means that from its perspective the value of -1 is a file
descriptor that needs to be freed by calling the deleter on it. Therefore, the deleter must be prepared to handle resource values that are
unallocated. Later on we will see how to mitigate this.
[tip Besides the resource value, you can specify the deleter object as the second argument of the [class_scope_unique_resource] constructor.
In fact, the second argument is mandatory if the deleter is not default-constructible or if its default constructor may throw. This is
necessary to be able to call the deleter on the passed resource value, should the construction of either the resource or the deleter throw.
This ensures that the resource doesn't leak in case if [class_scope_unique_resource] initialization fails.]
Second, we still need to check if opening the file succeeded before using it. Since [class_scope_unique_resource] in this example is always
constructed in allocated state, we have to check the file descriptor value for being negative. In order to access the resource value one
can use the `get` method of [class_scope_unique_resource].
We can improve this example by making [class_scope_unique_resource] initialization automatically check for the file descriptor validity.
This will only require modifying `fd_deleter` and `open_file`, the rest of the code stays unchanged.
// A deleter for POSIX-like file descriptors
struct fd_deleter
{
void operator() (int fd) const noexcept
{
close(fd);
}
};
// Opens a file with the given filename for reading and returns wrapped file descriptor
boost::scope::unique_resource< int, fd_deleter > open_file(std::string const& filename)
{
auto file = boost::scope::make_unique_resource_checked(
open(filename.c_str(), O_RDONLY)
-1,
fd_deleter());
if (!file.allocated())
{
int err = errno;
throw std::system_error(err, std::generic_category(), "Failed to open file " + filename);
}
return file;
}
Here, the `make_unique_resource_checked` helper function performs the following:
# If the resource value, which is the first argument - the result of the `open` call, is equal to the invalid resource value, which is
the second argument, creates and returns an unallocated [class_scope_unique_resource] object.
# Otherwise, creates and returns an allocated [class_scope_unique_resource] object that wraps the result of the `open` call.
# In both cases, the third argument is used to initialize the deleter in the returned [class_scope_unique_resource] object.
Note that if `open` fails and returns -1, the deleter is guaranteed not to be called on this resource value - neither by
`make_unique_resource_checked` nor by [class_scope_unique_resource], not even if [class_scope_unique_resource] construction fails with an
exception. This allows us to simplify `fd_deleter` and remove the check for negative `fd` values.
[tip If C++17 is supported, it is possible to use __boost_core_functor__ from __boost_core__ to wrap raw functions like `close` into
a function object that can be specified as a deleter in `unique_resource`. For example, `fd_deleter` from the example above could be
replaced with `boost::core::functor< close >`.]
Furthermore, since the returned [class_scope_unique_resource] object now properly indicates whether the resource is allocated or not,
we can use the `allocated` member function to test it instead of checking the resource value. This makes the code more readable.
[note In order to avoid confusion with the [class_scope_unique_resource] object being always allocated after construction with a resource
value, it is recommended to always use either the `make_unique_resource_checked` factory function or, more preferably, resource traits,
as described in the next section. Resource traits are the preferred solution as it makes [class_scope_unique_resource] more efficient and
less error-prone to use.]
[section:resource_traits Resource traits]
The [class_scope_unique_resource] class template also supports an optional third template parameter, which can be used to specify a resource
traits class. Resource traits provide [class_scope_unique_resource] with additional knowledge about the resource features that allow for
a more efficient implementation. This is useful when there is one or more resource value that is considered unallocated, that is such a value
that does not identify an allocated resource that needs to be freed. For example, for pointer resource types, null is usually considered as
the unallocated value, and for POSIX-like file descriptors, all negative values are unallocated values, since no valid file descriptor
can be negative.
If `Resource` is the resource type specified in [class_scope_unique_resource] template parameters then, if specified, resource traits must
be a class type with the following public static member functions:
* `bool is_allocated(Resource const& r) noexcept` - must return `true` if the resource value `r` is an allocated resource value and `false`
otherwise.
* `R make_default() noexcept` - must return a value such that `std::is_constructible< Resource, R >::value && std::is_nothrow_assignable<
Resource&, R >::value` is `true` and constructing `Resource` from `R` produces an unallocated resource value that can be used to initialize
the default-constructed [class_scope_unique_resource] object.
Note that all listed member functions must be non-throwing. Given these definitions, calling `is_allocated` on the value returned by
`make_default` must always return `false`.
When the conforming resource traits are provided, [class_scope_unique_resource] behavior changes as follows:
* [class_scope_unique_resource] will no longer separately track whether it is in allocated state or not and instead will use `is_allocated`
on the wrapped resource value for this. In particular, this means that constructing [class_scope_unique_resource] from a resource value
or calling `reset` with a resource value may now produce a wrapper in an unallocated state, if the resource value is an unallocated value.
* Default constructor, `reset` with no arguments, move constructor and move assignment of [class_scope_unique_resource] will use `make_default`
to initialize the resource value in the unallocated wrapper (for move operations - in the move source).
Using the resource traits allow us to further improve the example given in the previous section. Again, our primary interest is `open_file`
and `unique_resource` type usage, the rest of `equal_files` implementation remains unchanged.
// A deleter for POSIX-like file descriptors
struct fd_deleter
{
void operator() (int fd) const noexcept
{
close(fd);
}
};
// Resource traits for POSIX-like file descriptors
struct fd_resource_traits
{
static bool is_allocated(int fd) noexcept
{
return fd >= 0;
}
static int make_default() noexcept
{
// Return any unallocated resource value
return -1;
}
};
// A shorthand type alias for unique file descriptor wrappers
using unique_fd = boost::scope::unique_resource< int, fd_deleter, fd_resource_traits >;
// Opens a file with the given filename for reading and returns wrapped file descriptor
unique_fd open_file(std::string const& filename)
{
unique_fd file(open(filename.c_str(), O_RDONLY));
if (!file.allocated())
{
int err = errno;
throw std::system_error(err, std::generic_category(), "Failed to open file " + filename);
}
return file;
}
In this piece of code, we no longer need to use `make_unique_resource_checked` every time we open a file (or otherwise create a file
descriptor), and therefore we don't have to duplicate the invalid file descriptor value or the deleter - this information is provided
by `fd_resource_traits` and conveniently embedded in the `unique_fd` type. As before, if `open` fails and returns -1, the constructed
[class_scope_unique_resource] object will be in an unallocated state, meaning that we can use `allocated` accessor, and that the deleter
will only be called if `open` succeeded.
[tip The `fd_deleter`, `fd_resource_traits` and `unique_fd` types presented in the examples above are provided by the library out of
the box in [boost_scope_fd_resource_hpp] and [boost_scope_unique_fd_hpp] headers.]
[endsect]
[section:simplified_resource_traits Simplified resource traits]
[note Components described in this section require a C++17 compiler that supports [@https://en.cppreference.com/w/cpp/language/template_parameters
`auto` non-type template parameters] and [@https://en.cppreference.com/w/cpp/language/fold fold expressions].]
The library provides an `unallocated_resource` class template that can be used to generate resource traits for use with `unique_resource`
when the resource satisfies the following constraints:
* Resource values are allowed to be specified as [@https://en.cppreference.com/w/cpp/language/template_parameters non-type template parameters]
in C++. The exact set of types that meet this requirement depends on the C++ standard version being used. For example, integers,
enumerations, pointers and lvalue references are supported since C++17. C++20 adds support for class types. Note that resource value
initialization expression must be a constant expression. In particular, for pointers and references this means that the resource value
initialization expression must refer to an object or function or, in case of pointers, produce a null pointer.
* There is one or more unallocated resource values that can be individually listed. All other resource values represent allocated resources
that need to be freed.
* One of these unallocated resource values is considered the default.
* Resource type supports move construction and assignment from the default value, as well as comparison for equality and inequality with
unallocated values, and none of these operations throw exceptions.
When the above requirements are met, one can specify the unallocated resource values as non-type template parameters of `unallocated_resource` to
generate resource traits for `unique_resource`. The first of the listed unallocated values is the default.
For example, let's consider resource traits definition for Windows [@https://learn.microsoft.com/en-us/windows/win32/sysinfo/handles-and-objects
handles].
struct handle_traits
{
//! Returns the default resource value
static HANDLE make_default() noexcept
{
return INVALID_HANDLE_VALUE;
}
//! Tests if \a res is an allocated resource value
static bool is_allocated(HANDLE res) noexcept
{
return res != INVALID_HANDLE_VALUE && res != (HANDLE)NULL;
}
};
Here, `INVALID_HANDLE_VALUE` is a special constant returned by some Windows API functions that indicates no allocated resource associated with
the handle. However, other functions, like [@https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
`OpenProcess`] or [@https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthread `OpenThread`] for example,
return a `NULL` handle in case of errors, and we have to test for that value as well.
[tip For the curious readers, there is an [@https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443 article] describing reasons for this
inconsistency between different Windows APIs.]
With `unallocated_resource`, the above resource traits could be reduced to this:
using handle_traits = boost::scope::unallocated_resource< INVALID_HANDLE_VALUE, (HANDLE)NULL >;
[note Given that `INVALID_HANDLE_VALUE` is defined as `(HANDLE)-1` in Windows SDK, and `HANDLE` is actually a pointer type, in pure C++
one should not be able to specify this value as a non-type template parameter because the pointer does not refer to an object. However, MSVC
accepts this code as an extension. With other compilers, one would still have to implement proper resource traits similar to `handle_traits`
in this case.]
We can also use __boost_core_functor__ to wrap the [@https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
`CloseHandle`] Windows API function, which is used to free handles, to define the resource deleter. Then the complete definition of
`unique_resource` would look like this:
using unique_handle = boost::scope::unique_resource<
HANDLE,
boost::core::functor< CloseHandle >,
boost::scope::unallocated_resource< INVALID_HANDLE_VALUE, (HANDLE)NULL >
>;
[endsect]
[section:comparison_with_library_fundamentals_ts Comparison with `unique_resource` defined in C++ Extensions for Library Fundamentals]
The following sections provide comparison between `unique_resource` defined by [@https://cplusplus.github.io/fundamentals-ts/v3.html#scopeguard.uniqueres
C++ Extensions for Library Fundamentals TS] and this library.
[section:resource_traits Resource traits]
Boost.Scope supports an additional optional template parameter for [link scope.unique_resource.resource_traits resource traits]. This allows
`unique_resource` to become aware of the resource specifics, such as unallocated values and the default value, which is useful for usability and
efficiency. Specifying resource traits makes the `make_unique_resource_checked` factory function unnecessary, as `unique_resource` itself is
able to tell whether the resource is allocated upon construction. Additionally, this allows `unique_resource` to avoid storing an additional
flag that indicates whether the resource is is in an allocated state and needs to be freed upon destruction.
Using resource traits may change meaning of some `unique_resource` APIs, which is, arguably, for the better. Consider the following example.
// POSIX file descriptor resource
std::experimental::unique_resource< int, int (*)(int) > fd(-1, &close);
Since the deleter in this case must be specified in constructor parameters (because the default constructor would create a value-initialized
pointer to function, which is null and cannot be called), the user may be inclined to specify -1 for the file descriptor and expect that the
constructed `unique_resource` is in an unallocated state. This assumption would be incorrect because this constructor always creates an
allocated `unique_resource`, which means it will call `close(-1)` upon destruction. The correct way to construct `fd` in this case would be
to use `make_unique_resource_checked` like so:
std::experimental::unique_resource< int, int (*)(int) > fd = std::experimental::make_unique_resource_checked(-1, -1, &close);
Alternatively, one can call `release` immediately after constructing the `unique_resource`. Although it should be noted that this approach
is only valid if it is known that `unique_resource` construction cannot throw.
std::experimental::unique_resource< int, int (*)(int) > fd(-1, &close);
fd.release(); // mark the resource unallocated
A similar problem exists with the `reset` member function.
fd.reset(-1);
Contrary to expectation, this call does not leave `fd` in unallocated state and also causes `close(-1)` to be called on `fd` destruction. The
correct way to do this is to call `reset` without parameters or use `make_unique_resource_checked`.
fd.reset();
// or:
fd = std::experimental::make_unique_resource_checked(-1, -1, &close);
[note It may look like calling `reset(-1)` is an artificial example that doesn't happen in practice. It is not unusual for one to want to
combine another call with `reset` directly (e.g. `fd.reset(open(...))`) and expect this to work correctly. However, since `open` may return -1
in case of error, we will have the problem described above.]
Specifying resource traits changes meaning of the above examples. Both the constructor and `reset` from an unallocated resource value produce
a `unique_resource` object in an unallocated state.
boost::scope::unique_resource< int, int (*)(int), boost::scope::fd_resource_traits > fd(-1, &close);
assert(!fd.allocated());
fd.reset(-1);
assert(!fd.allocated());
[tip [class_scope_fd_resource_traits] is provided by Boost.Scope.]
Another benefit of resource traits is that the default resource value can be different from the value-constructed one. This can be useful if
the resource type is not default-constructible (for example, if it is a reference type) or the value-constructed resource is not an unallocated
value. For example, [class_scope_fd_resource_traits] specifies -1 as the default value for POSIX file descriptors because the value of 0 is a
valid allocated file descriptor.
There is no direct replacement for this feature the Library Fundamentals TS, although `make_unique_resource_checked` and `release` can be used
to work around the limitation in some cases, as shown above.
[endsect]
[section:constructors Constructor differences]
Unlike Library Fundamentals TS, Boost.Scope does not consider pointers to functions default-constructible for the purpose of deleters specified
in `unique_resource` template parameters. For example:
std::experimental::unique_resource< int, int (*)(int) > fd1; // compiles, creates unusable unique_resource object
boost::scope::unique_resource< int, int (*)(int) > fd2; // fails to compile, unique_resource is not default-constructible
This is because the default- or value-constructed pointer to function is not callable, as it is garbage or null pointer, respectively.
`unique_resource` does not provide a way to modify the deleter after construction, other than by move-assigning another `unique_resource` object,
which means the default-constructed `unique_resource` object would be unusable and potentially cause undefined behavior on `reset` or destruction.
Boost.Scope does allow omitting the deleter from constructor arguments when it truly is default-constructible and the default constructor doesn't
throw exceptions, while the TS requires both the resource value and the deleter to be specified.
std::experimental::unique_resource< int, boost::scope::fd_deleter > fd1(10); // fails to compile, deleter is missing in constructor arguments
boost::scope::unique_resource< int, boost::scope::fd_deleter > fd2(10); // ok, default-constructs fd_deleter
[tip [class_scope_fd_deleter] is provided by Boost.Scope.]
[note The requirement for the deleter default construction to be non-throwing is to ensure that the deleter can be invoked on the resource in
case if `unique_resource` construction fails with an exception. This prevents leaking the resource if its construction throws, or the deleter's
constructor throws.]
Boost.Scope allows omitting the resource value in `unique_resource` constructor arguments, when the deleter must be specified. `default_resource`
keyword can be used as a placeholder for the resource value in this case:
// Creates an unallocated unique_resource with the specified deleter
boost::scope::unique_resource< int, int (*)(int) > fd(boost::scope::default_resource, &close);
Note that in the example above, even though the resource value is value-initialized (i.e. zero), the `fd` is in an unallocated state and will not
call the deleter on destruction. `default_resource` also works with resource traits:
boost::scope::unique_resource< int, int (*)(int), boost::scope::fd_resource_traits > fd(boost::scope::default_resource, &close);
In the above case, the `fd` object will use the default value specified by the resource traits (i.e. -1, in case of fd_resource_traits) to initialize
its stored resource object. Again, the `fd` object is in an unallocated state after construction.
Library Fundamentals TS specifies that `unique_resource` move constructor must deallocate the resource if the resource is nothrow move-constructible
and the deleter is not and the deleter's copy constructor throws and exception. This is a case when `unique_resource` is half-constructed: the
resource has been moved into the object being constructed (i.e. is no longer stored in the source object) but the deleter fails to copy-construct
(i.e. it only exists in the source object). The behavior described in the TS ensures that the resource doesn't leak, but it leaves the source
`unique_resource` unallocated in case of exception, which means the move constructor only maintains
[@https://en.cppreference.com/w/cpp/language/exceptions#Exception_safety basic exception guarantee]. Boost.Scope in this case leaves the source
`unique_resource` in its original state, which also guarantees that the resource doesn't leak, but provides a strong exception guarantee.
[endsect]
[section:check_allocated Checking whether resource is allocated]
One glaring omission in the Library Fundamentals TS is that `unique_resource` doesn't provide a way to test whether the resource is in an allocated
state. Boost.Scope rectifies this by providing `allocated()` method, as well as contextual conversion to `bool`.
boost::scope::unique_resource< int, int (*)(int), boost::scope::fd_resource_traits > fd(-1, &close);
assert(!fd.allocated());
assert(!fd);
fd.reset(10);
assert(fd.allocated());
assert(!!fd);
[note The contextual conversion to `bool` in `unique_resource` does not test the resource /value/ and only tests whether the resource is allocated.
This is consistent with other components, such as `std::optional`, for example.]
[endsect]
[section:swap Native support for swapping]
Library Fundamentals TS defines `unique_resource` to be a move-constructible and move-assignable type, and through this property it supports the
standard `std::swap` algorithm. However, there are two issues with this approach:
* It prevents swapping algorithms specialized for the resource and deleter types from being used. Even if `swap` is specialized, the generic
algorithm involving moving or copying the resource and deleter objects is used.
* The generic `swap` implementation only provides [@https://en.cppreference.com/w/cpp/language/exceptions#Exception_safety basic exception guarantee].
The algorithm move-constructs a copy of one of the source objects and then performs two move-assignments. If either of the assignments fail, one
of the `unique_resource` objects will be left unallocated. It is also worth noting that the TS defines `unique_resource` move constructor to be
potentially destructive. If the resource type move constructor is non-throwing and the deleter copy-constructor throws, the deleter is called on
the move-constructed resource object to avoid leaking it. In the context of `std::swap` this means that even if the move constructor fails, it will
leave the source object unallocated rather than unmodified.
Boost.Scope provides native support for swapping, both as a member and non-member function `swap`. Swapping is supported if at least one of the
resource or deleter types support non-throwing swap (either default or specialized algorithm) and provides strong exception guarantee.
`unique_resource` swap operation is non-throwing if both resource and deleter types support non-throwing swap.
boost::scope::unique_resource< int, boost::scope::fd_deleter, boost::scope::fd_resource_traits > fd1, fd2;
fd1.swap(fd2);
swap(fd1, fd2); // found via ADL
[endsect]
[section:dereference Broader support for dereferencing]
Library Fundamentals TS `unique_resource` supports `operator*()` and `operator->()` for pointer resource types. Boost.Scope extends this support
for any resource types that support dereferencing.
// Resource object type
struct my_object
{
void foo();
};
// Manager for my_object instances
class my_object_manager
{
private:
// List of allocated objects
using my_object_list = std::list< my_object >;
// Resource handle that refers to a my_object element in the list
class my_handle
{
private:
my_object_list* m_list;
my_object_list::iterator m_it;
public:
my_handle() noexcept : m_list(nullptr), m_it() {}
explicit my_handle(my_object_list& list, my_object_list::iterator it) noexcept :
m_list(&list), m_it(it)
{
}
my_object_list::iterator operator-> () const noexcept /*< The operator relies on `operator->` chaining. >*/
{
return m_it;
}
my_object& operator* () const noexcept
{
return *m_it;
}
my_object_list* get_list() const noexcept
{
return m_list;
}
my_object_list::iterator get_iterator() const noexcept
{
return m_it;
}
};
// Resource deleter
struct my_handle_deleter
{
void operator() (my_handle const& res) const noexcept
{
res.get_list()->erase(res.get_iterator());
}
};
// Resource traits
struct my_handle_traits
{
static my_handle make_default() noexcept
{
return my_handle();
}
static bool is_allocated(my_handle const& res) noexcept
{
return res.get_list() != nullptr && res.get_iterator() != res.get_list()->end();
}
};
public:
// Unique resource wrapper for my_object instances
using my_object_handle = boost::scope::unique_resource< my_handle, my_handle_deleter, my_handle_traits >;
private:
my_object_list m_objects;
public:
// Allocates a new object and returns a handle to it
my_object_handle allocate_object()
{
return my_object_handle(my_handle(m_objects, m_objects.insert(m_objects.end(), my_object())));
}
};
void allocate_and_use_object(my_object_manager& mgr)
{
my_object_manager::my_object_handle handle = mgr.allocate_object();
if (handle)
handle->foo(); // invokes my_object::foo on the allocated object
}
In the above example, we're using `unique_resource` to reference objects created and maintained by `my_object_manager`. The `my_handle` class
is the resource type and supports accessing `my_object` members through `operator->` and `operator*`. This enables `operator->` and `operator*`
in `unique_resource`, which allows the code that uses `unique_resource` to access `my_object` members.
With Library Fundamentals TS `unique_resource`, this is possible to emulate by accessing the stored resource object via the `unique_resource::get`
accessor and dereferencing that.
[endsect]
[endsect]
[endsect]