2
0
mirror of https://github.com/boostorg/scope.git synced 2026-01-19 04:42:10 +00:00

Enforced correctness checks for resource traits, improved docs.

Instead of silently switching to the unique_resource implementation
based on the "allocated" flag if the resource traits don't conform
to the requirements, issue hard compilation errors. This should better
protect against mistakes, where the user assumes unique_resource
behaves as if it is using the resource traits while it actually doesn't.

Improved documentation of requirements for resource traits and documented
the "reduced" form of resource traits, where the traits only provide
the default resource value but otherwise don't change unique_resource
behavior.
This commit is contained in:
Andrey Semashev
2024-01-31 04:36:29 +03:00
parent bb295bcd77
commit d53d4f43af
9 changed files with 415 additions and 96 deletions

View File

@@ -33,6 +33,9 @@ Updates according to Boost [@https://lists.boost.org/Archives/boost/2024/01/2557
* [link scope.unique_resource `unique_resource`] move constructor was modified to preserve the original state of the
source argument in case of exception. This deviates from the TS behavior, which specifies to invoke the deleter on
the move-constructed resource, but it means the move constructor now maintains strong exception guarantee.
* Enforced compile-time correctness checks for [link scope.unique_resource `unique_resource`] resource traits specified
by user. Improved description of requirements for the resource traits. Documented the "reduced" resource traits that
only allow customizing the default resource value but do not otherwise change `unique_resource` behavior.
[heading 0.1]

View File

@@ -212,14 +212,19 @@ that does not identify an allocated resource that needs to be freed. For example
the unallocated value, and for POSIX-like file descriptors, all negative values are unallocated values, since no valid file descriptor
can be negative.
If specified, resource traits must be a class with the following public static member functions:
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(R 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 an unallocated resource value that can be used to initialize the default-constructed
[class_scope_unique_resource] object. Note that calling `is_allocated` on the value returned by `make_default` must always return `false`.
* `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 operations must be non-throwing. When the conforming resource traits are provided, [class_scope_unique_resource] behavior changes
as follows:
Note that all listed operations 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
@@ -279,6 +284,14 @@ 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_resource_hpp] and [boost_scope_unique_fd_hpp] headers.]
[class_scope_unique_resource] also supports a reduced form of resource traits consisting of only the `R make_default()` public static
member function, where `std::is_constructible< Resource, R >::value` is `true`. If resource traits are of this reduced form,
[class_scope_unique_resource] will use the `make_default` function to initialize the resource value in [class_scope_unique_resource]
default constructor, but otherwise will behave as if no resource traits were specified. In particular, [class_scope_unique_resource]
will not distinguish between allocated and unallocated wrapped resource values. This mode of operation may be useful in cases when the
resource type is not default-constructible, but there is a way to create an unallocated resource value that would allow
[class_scope_unique_resource] to be default-constructible.
[endsect]
[section:simplified_resource_traits Simplified resource traits]

View File

@@ -151,7 +151,7 @@ template< typename Resource, typename Traits >
struct has_custom_default_impl
{
template< typename Tr, typename R = decltype(Tr::make_default()) >
static typename std::is_constructible< Resource, R >::type _has_custom_default_check(int);
static std::true_type _has_custom_default_check(int);
template< typename Tr >
static std::false_type _has_custom_default_check(...);
@@ -159,8 +159,17 @@ struct has_custom_default_impl
};
// The type trait indicates whether the resource traits define a `make_default` static method
template< typename Resource, typename Traits, bool = has_custom_default_impl< Resource, Traits >::type::value >
struct has_custom_default : public std::false_type { };
template< typename Resource, typename Traits >
struct has_custom_default : public has_custom_default_impl< Resource, Traits >::type { };
struct has_custom_default< Resource, Traits, true > :
public std::true_type
{
using default_resource_type = decltype(Traits::make_default());
static_assert(std::is_constructible< Resource, default_resource_type >::value && std::is_assignable< Resource&, default_resource_type >::value,
"Invalid unique_resource resource traits: resource must be constructible and assignable from the default resource value");
};
template< typename Resource, typename Traits >
struct has_unallocated_state_impl
@@ -174,8 +183,16 @@ struct has_unallocated_state_impl
};
// The type trait indicates whether the resource traits define an `is_allocated` static method
template< typename Resource, typename Traits, bool = has_unallocated_state_impl< Resource, Traits >::type::value >
struct has_unallocated_state : public std::false_type { };
template< typename Resource, typename Traits >
struct has_unallocated_state : public has_unallocated_state_impl< Resource, Traits >::type { };
struct has_unallocated_state< Resource, Traits, true > :
public std::true_type
{
static_assert(noexcept(!!Traits::is_allocated(std::declval< Resource const& >())),
"Invalid unique_resource resource traits: is_allocated must be noexcept");
};
template< typename Resource, bool UseCompactStorage >
class resource_storage
@@ -551,11 +568,11 @@ struct use_unallocated_state : public std::false_type { };
template< typename Resource, typename Traits >
struct use_unallocated_state< Resource, Traits, true > :
public detail::conjunction<
std::integral_constant< bool, noexcept(Traits::make_default()) >,
std::is_nothrow_assignable< Resource&, decltype(Traits::make_default()) >
>
public std::true_type
{
static_assert(noexcept(Traits::make_default()), "Invalid unique_resource resource traits: make_default must be noexcept");
static_assert(std::is_nothrow_assignable< Resource&, decltype(Traits::make_default()) >::value,
"Invalid unique_resource resource traits: resource must be nothrow-assignable from the default resource value");
};
template< typename Resource, typename Deleter, typename Traits, bool = use_unallocated_state< Resource, Traits >::value >
@@ -1183,25 +1200,35 @@ struct dereference_traits< T, true >
* \li Resource objects can be tested for being unallocated. Such a test shall
* not throw exceptions.
*
* If specified, the resource traits must be a class that has the following
* If specified, the resource traits must be a class type that has the following
* public static members:
*
* \li `Resource make_default() noexcept` - must return the default resource
* value.
* \li `bool is_allocated(Resource const& res) noexcept` - must
* return \c true if \c res is not one of the unallocated resource values
* and \c false otherwise.
* \li `R make_default() noexcept` - must return the default resource value such
* that `std::is_constructible< Resource, R >::value &&
* std::is_nothrow_assignable< Resource&, R >::value` is \c true.
* \li `bool is_allocated(Resource const& res) noexcept` - must return \c true
* if \c res is not one of the unallocated resource values and \c false
* otherwise.
*
* Note that `is_allocated(make_default())` must always return \c false.
*
* When conforming resource traits are specified, \c unique_resource will be able
* to avoid storing additional indication of whether the owned resource object
* needs to be deallocated with the deleter on destruction. It will use the default
* resource value to initialize the owned resource object when \c unique_resource
* is not in the allocated state. Additionally, it will be possible to construct
* \c unique_resource with unallocated resource values, which will create
* \c unique_resource objects in unallocated state (the deleter will not be called
* on unallocated resource values).
* When resource traits satisfying the above requirements are specified,
* \c unique_resource will be able to avoid storing additional indication of
* whether the owned resource object needs to be deallocated with the deleter
* on destruction. It will use the default resource value to initialize the owned
* resource object when \c unique_resource is not in the allocated state.
* Additionally, it will be possible to construct \c unique_resource with
* unallocated resource values, which will create \c unique_resource objects in
* unallocated state (the deleter will not be called on unallocated resource
* values).
*
* `unique_resource` also supports a "reduced" form of resource traits, where
* the traits only provide a `R make_default()` public static member function, where
* `std::is_constructible< Resource, R >::value` is \c true. In this case,
* `unique_resource` will use `make_default` in its default constructor to
* initialize the stored resource object, but will otherwise behave as if no
* resource traits were specified. In particular, `unique_resource` will not
* distinguish between allocated and unallocated wrapped resource values.
*
* \tparam Resource Resource type.
* \tparam Deleter Resource deleter function object type.

View File

@@ -0,0 +1,41 @@
/*
* 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 unique_resource_bad_rtraits_isalloc_throwing.cpp
* \author Andrey Semashev
*
* \brief This file tests that \c unique_resource rejects resource
* traits where `is_allocated` is not `noexcept`.
*/
#include <boost/scope/unique_resource.hpp>
struct resource
{
resource(int) noexcept {}
resource& operator= (int) noexcept { return *this; };
};
struct resource_deleter
{
using result_type = void;
result_type operator() (resource const&) const noexcept {}
};
struct bad_resource_traits
{
static int make_default() noexcept { return 10; }
static bool is_allocated(resource const& res) { return false; }
};
int main()
{
boost::scope::unique_resource< resource, resource_deleter, bad_resource_traits > ur;
return 0;
}

View File

@@ -0,0 +1,42 @@
/*
* 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 unique_resource_bad_rtraits_mkdef_nonassignable.cpp
* \author Andrey Semashev
*
* \brief This file tests that \c unique_resource rejects resource
* traits where the resource cannot be assigned from the
* result of `make_default`.
*/
#include <boost/scope/unique_resource.hpp>
struct resource
{
resource(int) noexcept {}
resource& operator= (int) = delete;
};
struct resource_deleter
{
using result_type = void;
result_type operator() (resource const&) const noexcept {}
};
struct bad_resource_traits
{
static int make_default() noexcept { return 10; }
static bool is_allocated(resource const& res) noexcept { return false; }
};
int main()
{
boost::scope::unique_resource< resource, resource_deleter, bad_resource_traits > ur;
return 0;
}

View File

@@ -0,0 +1,42 @@
/*
* 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 unique_resource_bad_rtraits_mkdef_nonconstructible.cpp
* \author Andrey Semashev
*
* \brief This file tests that \c unique_resource rejects resource
* traits where the resource cannot be constructed from the
* result of `make_default`.
*/
#include <boost/scope/unique_resource.hpp>
struct resource
{
resource(int) = delete;
resource& operator= (int) noexcept { return *this; }
};
struct resource_deleter
{
using result_type = void;
result_type operator() (resource const&) const noexcept {}
};
struct bad_resource_traits
{
static int make_default() noexcept { return 10; }
static bool is_allocated(resource const& res) noexcept { return false; }
};
int main()
{
boost::scope::unique_resource< resource, resource_deleter, bad_resource_traits > ur;
return 0;
}

View File

@@ -0,0 +1,41 @@
/*
* 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 unique_resource_bad_rtraits_mkdef_throwing.cpp
* \author Andrey Semashev
*
* \brief This file tests that \c unique_resource rejects resource
* traits where `make_default` is not `noexcept`.
*/
#include <boost/scope/unique_resource.hpp>
struct resource
{
resource(int) noexcept {}
resource& operator= (int) noexcept { return *this; };
};
struct resource_deleter
{
using result_type = void;
result_type operator() (resource const&) const noexcept {}
};
struct bad_resource_traits
{
static int make_default() { return 10; }
static bool is_allocated(resource const& res) noexcept { return false; }
};
int main()
{
boost::scope::unique_resource< resource, resource_deleter, bad_resource_traits > ur;
return 0;
}

View File

@@ -0,0 +1,41 @@
/*
* 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 unique_resource_bad_rtraits_mkdef_nonconstructible.cpp
* \author Andrey Semashev
*
* \brief This file tests that \c unique_resource rejects reduced
* resource traits where the resource cannot be constructed
* from the result of `make_default`.
*/
#include <boost/scope/unique_resource.hpp>
struct resource
{
resource(int) = delete;
resource& operator= (int) noexcept { return *this; }
};
struct resource_deleter
{
using result_type = void;
result_type operator() (resource const&) const noexcept {}
};
struct bad_resource_traits
{
static int make_default() noexcept { return 10; }
};
int main()
{
boost::scope::unique_resource< resource, resource_deleter, bad_resource_traits > ur;
return 0;
}

View File

@@ -819,11 +819,16 @@ public:
{
}
copyable_resource(copyable_resource const&) = default;
copyable_resource& operator= (copyable_resource const&) = default;
copyable_resource(copyable_resource const& that) noexcept :
m_value(that.m_value)
{
}
copyable_resource(copyable_resource&&) = delete;
copyable_resource& operator= (copyable_resource&&) = delete;
copyable_resource& operator= (copyable_resource const& that) noexcept
{
m_value = that.m_value;
return *this;
}
int get() const noexcept
{
@@ -910,69 +915,6 @@ struct wrapped_int_resource_traits
}
};
// Specially-crafted resource type that is:
// * nothrow move-constructible
// * NOT-nothrow move-assignable
// * has data layout which favors placing the "allocated" flag in unique_resource in its tail padding
class move_constructible_resource
{
private:
int m_n1;
signed char m_n2;
public:
constexpr move_constructible_resource() noexcept : m_n1(0), m_n2(0) { }
explicit move_constructible_resource(int n1, signed char n2 = 0) noexcept : m_n1(n1), m_n2(n2) { }
move_constructible_resource(move_constructible_resource&& that) noexcept : m_n1(that.m_n1), m_n2(that.m_n2)
{
that.m_n1 = 0;
that.m_n2 = 0;
}
move_constructible_resource(move_constructible_resource const& that) : m_n1(that.m_n1), m_n2(that.m_n2)
{
}
move_constructible_resource& operator= (move_constructible_resource&& that) // not noexcept
{
m_n1 = that.m_n1;
m_n2 = that.m_n2;
that.m_n1 = 0;
that.m_n2 = 0;
return *this;
}
move_constructible_resource& operator= (move_constructible_resource const& that)
{
m_n1 = that.m_n1;
m_n2 = that.m_n2;
return *this;
}
bool operator== (move_constructible_resource const& that) const noexcept
{
return m_n1 == that.m_n1 && m_n2 == that.m_n2;
}
bool operator!= (move_constructible_resource const& that) const noexcept
{
return !operator==(that);
}
friend std::ostream& operator<< (std::ostream& strm, move_constructible_resource const& res)
{
strm << "{ " << res.m_n1 << ", " << static_cast< int >(res.m_n2) << " }";
return strm;
}
friend void copy_resource(move_constructible_resource const& from, move_constructible_resource& to)
{
to.m_n1 = from.m_n1;
to.m_n2 = from.m_n2;
}
};
template< template< typename > class Traits >
void check_throw_deleter()
{
@@ -1137,13 +1079,102 @@ void check_throw_deleter()
BOOST_TEST_EQ(deleted_res1, moveable_resource{ 10 });
BOOST_TEST_EQ(deleted_res2, moveable_resource{ 20 });
}
}
n = 0;
// Specially-crafted resource type that is:
// * nothrow move-constructible
// * NOT-nothrow move-assignable
// * has data layout which favors placing the "allocated" flag in unique_resource in its tail padding
class move_constructible_resource
{
public:
// A special tag to construct "default" resource value
struct default_tag { };
private:
int m_n1;
signed char m_n2;
public:
constexpr move_constructible_resource() noexcept : m_n1(0), m_n2(0) { }
// For compatibility with move_constructible_resource_traits::make_default()
explicit constexpr move_constructible_resource(default_tag) noexcept : move_constructible_resource() { }
explicit move_constructible_resource(int n1, signed char n2 = 0) noexcept : m_n1(n1), m_n2(n2) { }
move_constructible_resource(move_constructible_resource&& that) noexcept : m_n1(that.m_n1), m_n2(that.m_n2)
{
that.m_n1 = 0;
that.m_n2 = 0;
}
move_constructible_resource(move_constructible_resource const& that) : m_n1(that.m_n1), m_n2(that.m_n2)
{
}
move_constructible_resource& operator= (move_constructible_resource&& that) // not noexcept
{
m_n1 = that.m_n1;
m_n2 = that.m_n2;
that.m_n1 = 0;
that.m_n2 = 0;
return *this;
}
move_constructible_resource& operator= (move_constructible_resource const& that)
{
m_n1 = that.m_n1;
m_n2 = that.m_n2;
return *this;
}
// For compatibility with move_constructible_resource_traits::make_default()
move_constructible_resource& operator= (default_tag) noexcept
{
m_n1 = 0;
m_n2 = 0;
return *this;
}
bool operator== (move_constructible_resource const& that) const noexcept
{
return m_n1 == that.m_n1 && m_n2 == that.m_n2;
}
bool operator!= (move_constructible_resource const& that) const noexcept
{
return !operator==(that);
}
friend std::ostream& operator<< (std::ostream& strm, move_constructible_resource const& res)
{
strm << "{ " << res.m_n1 << ", " << static_cast< int >(res.m_n2) << " }";
return strm;
}
friend void copy_resource(move_constructible_resource const& from, move_constructible_resource& to)
{
to.m_n1 = from.m_n1;
to.m_n2 = from.m_n2;
}
};
//! Resource traits for \c move_constructible_resource
struct move_constructible_resource_traits
{
static move_constructible_resource::default_tag make_default() noexcept { return move_constructible_resource::default_tag{}; }
static bool is_allocated(move_constructible_resource const& res) noexcept { return res != move_constructible_resource{}; }
};
void check_throw_deleter_move_constructible_resource()
{
int n = 0;
{
move_constructible_resource deleted_res1;
try
{
using unique_resource_t = boost::scope::unique_resource< move_constructible_resource, throwing_resource_deleter< move_constructible_resource >, Traits< move_constructible_resource > >;
using unique_resource_t = boost::scope::unique_resource< move_constructible_resource, throwing_resource_deleter< move_constructible_resource >, move_constructible_resource_traits >;
unique_resource_t ur1{ move_constructible_resource(10, 5), throwing_resource_deleter< move_constructible_resource >(deleted_res1, n) };
ur1.get_deleter().set_throw(true);
try
@@ -1320,6 +1351,14 @@ struct int_resource_traits
}
};
struct reduced_int_resource_traits
{
static int make_default()
{
return -1;
}
};
void check_resource_traits()
{
{
@@ -1471,6 +1510,35 @@ void check_resource_traits()
BOOST_TEST_EQ(n, 2);
BOOST_TEST_EQ(deleted_res1, 10);
BOOST_TEST_EQ(deleted_res2, 20);
// Test reduced resource traits
{
boost::scope::unique_resource< int, empty_resource_deleter< int >, reduced_int_resource_traits > ur;
BOOST_TEST_EQ(ur.get(), reduced_int_resource_traits::make_default());
BOOST_TEST(!ur.allocated());
}
n = 0;
deleted_res1 = ~reduced_int_resource_traits::make_default();
{
boost::scope::unique_resource< int, checking_resource_deleter< int >, reduced_int_resource_traits > ur{ reduced_int_resource_traits::make_default(), checking_resource_deleter< int >(deleted_res1, n) };
BOOST_TEST_EQ(ur.get(), reduced_int_resource_traits::make_default());
BOOST_TEST(ur.allocated());
BOOST_TEST(!!ur);
}
BOOST_TEST_EQ(n, 1);
BOOST_TEST_EQ(deleted_res1, reduced_int_resource_traits::make_default());
n = 0;
deleted_res1 = -1;
{
boost::scope::unique_resource< int, checking_resource_deleter< int >, reduced_int_resource_traits > ur{ -10, checking_resource_deleter< int >(deleted_res1, n) };
BOOST_TEST_EQ(ur.get(), -10);
BOOST_TEST(ur.allocated());
BOOST_TEST(!!ur);
}
BOOST_TEST_EQ(n, 1);
BOOST_TEST_EQ(deleted_res1, -10);
}
#if !defined(BOOST_NO_CXX17_FOLD_EXPRESSIONS) && !defined(BOOST_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS)
@@ -1577,6 +1645,7 @@ int main()
check_throw_resource();
check_throw_deleter< default_resource_traits >();
check_throw_deleter< wrapped_int_resource_traits >();
check_throw_deleter_move_constructible_resource();
check_deduction();
check_resource_traits();
#if !defined(BOOST_NO_CXX17_FOLD_EXPRESSIONS) && !defined(BOOST_NO_CXX17_AUTO_NONTYPE_TEMPLATE_PARAMS)