...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
#include <boost/scope/unique_resource.hpp
>
Boost.Scope provides a 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,
unique_resource
can wrap other types of resources as well.
A resource type must have the following properties to be compatible with unique_resource
:
noexcept
, or
Note that if the resource type is a reference, the referenced object must be
stored externally to the 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
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 unique_resource
destructor.
Like std::unique_ptr
, 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 strong
exception guarantee for the swap
operation). The requirements are listed for each operation in the unique_resource
class reference.
![]() |
Tip |
---|---|
If the resource type is dereferenceable (i.e. supports unary |
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 |
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
unique_resource
object with the value returned by open
,
which may be -1 indicating an error. By default, 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 |
Second, we still need to check if opening the file succeeded before using it.
Since 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 unique_resource
.
We can improve this example by making 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:
open
call, is equal to
the invalid resource value, which is the second argument, creates and returns
an unallocated unique_resource
object.
unique_resource
object that wraps the result of the open
call.
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 unique_resource
,
not even if 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 |
Furthermore, since the returned 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 |
The unique_resource
class template also supports an optional third template parameter, which
can be used to specify a resource traits class. Resource traits provide
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 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 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, unique_resource
behavior changes as follows:
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 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.
reset
with no arguments, move constructor and move assignment of 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 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 |
![]() |
Note |
---|---|
Components described in this section require a C++17 compiler that supports
|
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:
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 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 OpenProcess
or 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 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 |
We can also use functor
to wrap the 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 > >;
The following sections provide comparison between unique_resource
defined by C++
Extensions for Library Fundamentals TS and this library.
Boost.Scope supports an additional optional template parameter for 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 |
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 |
---|---|
|
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, 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.
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 |
---|---|
|
![]() |
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 |
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 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.
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 |
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:
swap
is specialized, the generic algorithm involving moving or copying the
resource and deleter objects is used.
swap
implementation
only provides 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
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{ 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.