From d45a6255aae23fa5c2b3241a82d82455f468507f Mon Sep 17 00:00:00 2001 From: Andrey Semashev Date: Mon, 22 Jan 2024 02:02:28 +0300 Subject: [PATCH] Added unallocated_resource for simplified declaration of resource traits. 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. --- doc/changelog.qbk | 3 + doc/unique_resource.qbk | 66 +++++++++++++++- include/boost/scope/detail/config.hpp | 14 ++++ include/boost/scope/unique_resource.hpp | 43 +++++++++- test/run/unique_resource.cpp | 101 +++++++++++++++++++++++- 5 files changed, 221 insertions(+), 6 deletions(-) diff --git a/doc/changelog.qbk b/doc/changelog.qbk index 0b47706..cdd2458 100644 --- a/doc/changelog.qbk +++ b/doc/changelog.qbk @@ -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] diff --git a/doc/unique_resource.qbk b/doc/unique_resource.qbk index 07d18c5..28bff15 100644 --- a/doc/unique_resource.qbk +++ b/doc/unique_resource.qbk @@ -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 @@ -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] diff --git a/include/boost/scope/detail/config.hpp b/include/boost/scope/detail/config.hpp index 26821ea..7d8f45a 100644 --- a/include/boost/scope/detail/config.hpp +++ b/include/boost/scope/detail/config.hpp @@ -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__ diff --git a/include/boost/scope/unique_resource.hpp b/include/boost/scope/unique_resource.hpp index 603c1e0..bc0342e 100644 --- a/include/boost/scope/unique_resource.hpp +++ b/include/boost/scope/unique_resource.hpp @@ -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 @@ -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 { diff --git a/test/run/unique_resource.cpp b/test/run/unique_resource.cpp index 9e6dba0..88f6360 100644 --- a/test/run/unique_resource.cpp +++ b/test/run/unique_resource.cpp @@ -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 @@ -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(); @@ -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(); }