Skip to content

Commit

Permalink
Added unallocated_resource for simplified declaration of resource tra…
Browse files Browse the repository at this point in the history
…its.

A new unallocated_resource class template allows to automatically generate
resource traits from one or more unallocated resource values.

The idea for a more compact unique_resource declaration was given
by Janko Dedic in his review.
  • Loading branch information
Lastique committed Jan 23, 2024
1 parent fa650d5 commit d45a625
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 6 deletions.
3 changes: 3 additions & 0 deletions doc/changelog.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ Updates according to Boost [@https://lists.boost.org/Archives/boost/2024/01/2557
* In `unique_resource`, added `explicit operator bool` as a way to test if the resource in an allocated state, similar
to the `allocated` method. This was suggested by Dmitry Arkhipov before the review. Note that the operator does not
test the resource value, which is similar to `std::optional`.
* Added [link scope.unique_resource.simplified_resource_traits `unallocated_resource`] class template for simplifying
declaration of resource traits for `unique_resource`. The idea of a more compact `unique_resource` declaration was
presented by Janko Dedic in his [@https://lists.boost.org/Archives/boost/2023/11/255424.php review].

[heading 0.1]

Expand Down
66 changes: 65 additions & 1 deletion doc/unique_resource.qbk
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[/
/ Copyright 2023 Andrey Semashev
/ Copyright 2023-2024 Andrey Semashev
/
/ Distributed under the Boost Software License, Version 1.0.
/ (See accompanying file LICENSE_1_0.txt or copy at
Expand Down Expand Up @@ -281,4 +281,68 @@ 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 generally supported.
* There is one or more unallocated resource values that can be individually listed. Every other resource value represents an allocated resource
that needs to be freed.
* One of these unallocated resource values is considered the default.
* Resource type supports move construction and assignment, as well as comparison for equality and inequality, 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 most Windows API functions that indicates no allocated resource associated with
the handle. However, some 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. With `unallocated_resource`, the above resource traits could be
reduced to this:

using handle_traits = boost::scope::unallocated_resource< INVALID_HANDLE_VALUE, (HANDLE)NULL >;

Given that [@https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle `CloseHandle`] Windows API function is used to
free handles, the complete definition of `unique_resource` could look like this:

struct handle_deleter
{
void operator() (HANDLE h) const noexcept
{
CloseHandle(h);
}
};

using unique_handle = boost::scope::unique_resource<
HANDLE,
handle_deleter,
boost::scope::unallocated_resource< INVALID_HANDLE_VALUE, (HANDLE)NULL >
>;

[endsect]

[endsect]
14 changes: 14 additions & 0 deletions include/boost/scope/detail/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@
#define BOOST_SCOPE_NO_CXX17_NOEXCEPT_FUNCTION_TYPES
#endif

#if !(defined(__cpp_nontype_template_parameter_auto) && __cpp_nontype_template_parameter_auto >= 201606l) && \
!(defined(BOOST_GCC_VERSION) && BOOST_GCC_VERSION >= 70100 && __cplusplus >= 201703l) && \
!(defined(BOOST_CLANG) && BOOST_CLANG_VERSION >= 40000 && __cplusplus >= 201406l /* non-standard value that is greater than 201402, which is reported by clang up to 4.0.0 for C++1z */) && \
!(defined(BOOST_MSVC) && BOOST_MSVC >= 1914 && BOOST_CXX_VERSION >= 201703l)
#define BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO
#endif

#if defined(BOOST_NO_CXX17_FOLD_EXPRESSIONS) && \
!(defined(BOOST_GCC_VERSION) && BOOST_GCC_VERSION >= 60100 && __cplusplus >= 201703l) && \
!(defined(BOOST_CLANG) && BOOST_CLANG_VERSION >= 30900 && __cplusplus >= 201406l /* non-standard value that is greater than 201402, which is reported by clang up to 4.0.0 for C++1z */) && \
!(defined(BOOST_MSVC) && BOOST_MSVC >= 1912 && BOOST_CXX_VERSION >= 201703l)
#define BOOST_SCOPE_NO_CXX17_FOLD_EXPRESSIONS
#endif

#if !defined(BOOST_SCOPE_DETAIL_DOC_ALT)
#if !defined(BOOST_SCOPE_DOXYGEN)
#define BOOST_SCOPE_DETAIL_DOC_ALT(alt, ...) __VA_ARGS__
Expand Down
43 changes: 40 additions & 3 deletions include/boost/scope/unique_resource.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* (See accompanying file LICENSE_1_0.txt or copy at
* https://www.boost.org/LICENSE_1_0.txt)
*
* Copyright (c) 2022 Andrey Semashev
* Copyright (c) 2022-2024 Andrey Semashev
*/
/*!
* \file scope/unique_resource.hpp
Expand Down Expand Up @@ -38,10 +38,47 @@
namespace boost {
namespace scope {

struct default_resource_t {};
#if !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO) && !defined(BOOST_SCOPE_NO_CXX17_FOLD_EXPRESSIONS)

/*!
* \brief Simple resource traits for one or more unallocated resource values.
*
* This class template generates resource traits for `unique_resource` that specify
* one or more unallocated resource values. The first value, specified in the \c DefaultValue
* non-type template parameter, is considered the default. The other values, listed in
* \c UnallocatedValues, are optional. Any resource values other than \c DefaultValue
* or listed in \c UnallocatedValues are considered as allocated.
*
* In order for the generated resource traits to enable optimized implementation of
* `unique_resource`, the resource type must support non-throwing construction and assignment
* from, and comparison for (in)equality with \c DefaultValue or any of the resource
* values listed in \c UnallocatedValues.
*/
template< auto DefaultValue, decltype(DefaultValue)... UnallocatedValues >
struct unallocated_resource
{
//! Resource type
typedef decltype(DefaultValue) resource_type;

//! Returns the default resource value
static resource_type make_default() noexcept(std::is_nothrow_move_constructible< resource_type >::value)
{
return DefaultValue;
}

//! Tests if \a res is an allocated resource value (i.e. not the default)
static bool is_allocated(resource_type const& res) noexcept(noexcept(res != DefaultValue))
{
return res != DefaultValue && (... && (res != UnallocatedValues));
}
};

#endif // !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO) && !defined(BOOST_SCOPE_NO_CXX17_FOLD_EXPRESSIONS)

struct default_resource_t { };

//! Keyword representing default, unallocated resource argument
BOOST_INLINE_VARIABLE constexpr default_resource_t default_resource = {};
BOOST_INLINE_VARIABLE constexpr default_resource_t default_resource = { };

namespace detail {

Expand Down
101 changes: 99 additions & 2 deletions test/run/unique_resource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* (See accompanying file LICENSE_1_0.txt or copy at
* https://www.boost.org/LICENSE_1_0.txt)
*
* Copyright (c) 2023 Andrey Semashev
* Copyright (c) 2023-2024 Andrey Semashev
*/
/*!
* \file unique_resource.cpp
Expand Down Expand Up @@ -1349,6 +1349,101 @@ void check_resource_traits()
BOOST_TEST_EQ(deleted_res2, 20);
}

#if !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO) && !defined(BOOST_SCOPE_NO_CXX17_FOLD_EXPRESSIONS)

struct global_deleter_int
{
void operator() (int) noexcept
{
++g_n;
}
};

struct global_deleter_ptr
{
void operator() (int*) noexcept
{
++g_n;
}
};

void check_simple_resource_traits()
{
g_n = 0;
{
boost::scope::unique_resource< int, global_deleter_int, boost::scope::unallocated_resource< -1 > > ur;
BOOST_TEST_EQ(ur.get(), -1);
BOOST_TEST(!ur.allocated());
BOOST_TEST(!ur);
}
BOOST_TEST_EQ(g_n, 0);

g_n = 0;
{
boost::scope::unique_resource< int, global_deleter_int, boost::scope::unallocated_resource< -1 > > ur{ -1 };
BOOST_TEST_EQ(ur.get(), -1);
BOOST_TEST(!ur.allocated());
BOOST_TEST(!ur);
}
BOOST_TEST_EQ(g_n, 0);

g_n = 0;
{
boost::scope::unique_resource< int, global_deleter_int, boost::scope::unallocated_resource< -1 > > ur{ 10 };
BOOST_TEST_EQ(ur.get(), 10);
BOOST_TEST(ur.allocated());
BOOST_TEST(!!ur);
}
BOOST_TEST_EQ(g_n, 1);

g_n = 0;
{
boost::scope::unique_resource< int, global_deleter_int, boost::scope::unallocated_resource< -1, -2, -3 > > ur;
BOOST_TEST_EQ(ur.get(), -1);
BOOST_TEST(!ur.allocated());
BOOST_TEST(!ur);

ur.reset(-2);
BOOST_TEST_EQ(g_n, 0);
BOOST_TEST_EQ(ur.get(), -2);
BOOST_TEST(!ur.allocated());
BOOST_TEST(!ur);

ur.reset(-3);
BOOST_TEST_EQ(g_n, 0);
BOOST_TEST_EQ(ur.get(), -3);
BOOST_TEST(!ur.allocated());
BOOST_TEST(!ur);

ur.reset(10);
BOOST_TEST_EQ(g_n, 0);
BOOST_TEST_EQ(ur.get(), 10);
BOOST_TEST(ur.allocated());
BOOST_TEST(!!ur);
}
BOOST_TEST_EQ(g_n, 1);

g_n = 0;
{
boost::scope::unique_resource< int*, global_deleter_ptr, boost::scope::unallocated_resource< nullptr > > ur;
BOOST_TEST_EQ(ur.get(), static_cast< int* >(nullptr));
BOOST_TEST(!ur.allocated());
BOOST_TEST(!ur);
}
BOOST_TEST_EQ(g_n, 0);

g_n = 0;
{
boost::scope::unique_resource< int*, global_deleter_ptr, boost::scope::unallocated_resource< nullptr > > ur{ &g_n };
BOOST_TEST_EQ(ur.get(), &g_n);
BOOST_TEST(ur.allocated());
BOOST_TEST(!!ur);
}
BOOST_TEST_EQ(g_n, 1);
}

#endif // !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO) && !defined(BOOST_SCOPE_NO_CXX17_FOLD_EXPRESSIONS)

int main()
{
check_int();
Expand All @@ -1360,6 +1455,8 @@ int main()
check_throw_deleter< wrapped_int_resource_traits >();
check_deduction();
check_resource_traits();

#if !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO)
check_simple_resource_traits();
#endif
return boost::report_errors();
}

0 comments on commit d45a625

Please sign in to comment.