Skip to content

Commit

Permalink
Added utilities for simplified declaration of unique_resource.
Browse files Browse the repository at this point in the history
A new resource_deleter template allows to generate a deleter function
object from a reference to function. A new default_resource_value
template allows to automatically generate resource traits from the
default resource value, provided that no other resource values are
invalid.

The idea for a more compact unique_resource declaration was given
by Janko Dedic in his review.
  • Loading branch information
Lastique committed Jan 21, 2024
1 parent fa650d5 commit fc69eee
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 4 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 utilities for [link scope.unique_resource.simplified_declaration simplified declaration] of `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
63 changes: 62 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,65 @@ the box in [boost_scope_fd_resource_hpp] and [boost_scope_unique_fd_hpp] headers

[endsect]

[section:simplified_declaration Simplified declaration of `unique_resource`]

#include <``[boost_scope_resource_deleter_hpp]``>
#include <``[boost_scope_default_resource_value_hpp]``>

[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].]

In cases when resource deleter is implemented as a function (e.g. part of a third-party API), in order to use it with `unique_resource` one
typically has to write a function object wrapper that simply forwards the call to the function. For example, in order to wrap
[@https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle `CloseHandle`] Windows API function one would
typically have to write a deleter such as this:

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

The library provides a `resource_deleter` class template that allows to avoid having to write this wrapper, provided that the function meets the
following requirements:

* It must be callable with a single argument of the resource type.
* Its returned value must be ignorable or the return type must be `void`.
* It must not throw exceptions.

The above requirements imply that the deleter function must not fail for valid resource values. With `resource_deleter`, the `handle_deleter`
example can be rewritten as the following one-liner:

using handle_deleter = boost::scope::resource_deleter< CloseHandle >;

[tip Given that `resource_deleter` generates a unary function object, it can be used not only with `unique_resource` but also in other
contexts that require a deleter. For example, with smart-pointers such as `std::unique_ptr`.]

Additionally, the library provides a `default_resource_value` class template that can be used to generate resource traits for use with
`unique_resource` when the resource satisfies the following requirements:

* 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 exactly one unallocated resource value, and every other value is considered allocated.
* That unallocated resource value 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 default resource value as `default_resource_value` non-type template parameter to generate
resource traits for `unique_resource`.

The two class templates can be combined together to simplify `unique_resource` declaration. For example, one could define a `unique_resource` for
Windows handles like this:

using unique_handle = boost::scope::unique_resource<
HANDLE,
boost::scope::resource_deleter< CloseHandle >,
boost::scope::default_resource_value< INVALID_HANDLE_VALUE >
>;

[endsect]

[endsect]
68 changes: 68 additions & 0 deletions include/boost/scope/default_resource_value.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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)
*
* Copyright (c) 2024 Andrey Semashev
*/
/*!
* \file scope/default_resource_value.hpp
*
* This header contains definition of \c default_resource_value template.
*/

#ifndef BOOST_SCOPE_DEFAULT_RESOURCE_VALUE_HPP_INCLUDED_
#define BOOST_SCOPE_DEFAULT_RESOURCE_VALUE_HPP_INCLUDED_

#include <type_traits>
#include <boost/scope/detail/config.hpp>
#include <boost/scope/detail/header.hpp>

#ifdef BOOST_HAS_PRAGMA_ONCE
#pragma once
#endif

namespace boost {
namespace scope {

#if !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO)

/*!
* \brief Simple resource traits for a single default resource value.
*
* This class template generates resource traits for `unique_resource` that specify
* a single invalid resource value, which is also the default resource value, equal to
* \c DefaultResourceValue. That is, any resource value other than \c DefaultResourceValue
* is considered an allocated resource that needs to be freed.
*
* 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 DefaultResourceValue.
*/
template< auto DefaultResourceValue >
struct default_resource_value
{
//! Resource type
typedef decltype(DefaultResourceValue) resource_type;

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

//! 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 != DefaultResourceValue))
{
return res != DefaultResourceValue;
}
};

#endif // !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO)

} // namespace scope
} // namespace boost

#include <boost/scope/detail/footer.hpp>

#endif // BOOST_SCOPE_DEFAULT_RESOURCE_VALUE_HPP_INCLUDED_
7 changes: 7 additions & 0 deletions include/boost/scope/detail/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
#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 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_SCOPE_DETAIL_DOC_ALT)
#if !defined(BOOST_SCOPE_DOXYGEN)
#define BOOST_SCOPE_DETAIL_DOC_ALT(alt, ...) __VA_ARGS__
Expand Down
61 changes: 61 additions & 0 deletions include/boost/scope/resource_deleter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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)
*
* Copyright (c) 2024 Andrey Semashev
*/
/*!
* \file scope/resource_deleter.hpp
*
* This header contains definition of \c resource_deleter template.
*/

#ifndef BOOST_SCOPE_RESOURCE_DELETER_HPP_INCLUDED_
#define BOOST_SCOPE_RESOURCE_DELETER_HPP_INCLUDED_

#include <boost/scope/detail/config.hpp>
#include <boost/scope/detail/header.hpp>

#ifdef BOOST_HAS_PRAGMA_ONCE
#pragma once
#endif

namespace boost {
namespace scope {

#if !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO)

/*!
* \brief Simple resource deleter wrapper.
*
* This wrapper generates a resource deleter function object that calls \c Deleter
* to free the resource. \c Deleter must be callable with the single argument of
* the resource type, and its returned value must be ignorable. The call must not throw
* exceptions.
*
* Typically, this wrapper is used to generate resource deleter function objects from
* plain functions.
*/
template< auto Deleter >
struct resource_deleter
{
//! Function object return type
typedef void result_type;

//! Invokes \c Deleter on \a res
template< typename Resource >
result_type operator() (Resource&& res) const noexcept
{
Deleter(static_cast< Resource&& >(res));
}
};

#endif // !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO)

} // namespace scope
} // namespace boost

#include <boost/scope/detail/footer.hpp>

#endif // BOOST_SCOPE_RESOURCE_DELETER_HPP_INCLUDED_
2 changes: 1 addition & 1 deletion 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
72 changes: 70 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 All @@ -13,6 +13,10 @@
*/

#include <boost/scope/unique_resource.hpp>
#if !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO)
#include <boost/scope/resource_deleter.hpp>
#include <boost/scope/default_resource_value.hpp>
#endif
#include <boost/core/lightweight_test.hpp>
#include <boost/core/lightweight_test_trait.hpp>
#include <boost/config.hpp>
Expand Down Expand Up @@ -1349,6 +1353,68 @@ void check_resource_traits()
BOOST_TEST_EQ(deleted_res2, 20);
}

#if !defined(BOOST_SCOPE_NO_CXX17_NONTYPE_TEMPLATE_PARAMETER_AUTO)

void global_raw_func_deleter_int(int) noexcept
{
++g_n;
}

void global_raw_func_deleter_ptr(int*) noexcept
{
++g_n;
}

void check_simple_resource_traits()
{
g_n = 0;
{
boost::scope::unique_resource< int, boost::scope::resource_deleter< global_raw_func_deleter_int >, boost::scope::default_resource_value< -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, boost::scope::resource_deleter< global_raw_func_deleter_int >, boost::scope::default_resource_value< -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, boost::scope::resource_deleter< global_raw_func_deleter_int >, boost::scope::default_resource_value< -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*, boost::scope::resource_deleter< global_raw_func_deleter_ptr >, boost::scope::default_resource_value< 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*, boost::scope::resource_deleter< global_raw_func_deleter_ptr >, boost::scope::default_resource_value< 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)

int main()
{
check_int();
Expand All @@ -1360,6 +1426,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 fc69eee

Please sign in to comment.