mirror of
https://github.com/boostorg/scope.git
synced 2026-01-19 04:42:10 +00:00
634 lines
34 KiB
Plaintext
634 lines
34 KiB
Plaintext
[/
|
|
/ 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_deleter_hpp], [boost_scope_fd_resource_traits_hpp] and [boost_scope_unique_fd_hpp] headers. Note that
|
|
`fd_deleter` provided by the library has support for platforms where `close` can be interrupted and needs to be restarted. This support
|
|
is omitted for brevity from the examples above.]
|
|
|
|
[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]
|