Bladeren bron

Merge branch 'dev/unique_resource' into development

main
offa 7 jaren geleden
bovenliggende
commit
33f2dbd0d3
2 gewijzigde bestanden met toevoegingen van 309 en 198 verwijderingen
  1. +139
    -70
      include/unique_resource.h
  2. +170
    -128
      test/UniqueResourceTest.cpp

+ 139
- 70
include/unique_resource.h Bestand weergeven

@@ -26,140 +26,209 @@
namespace sr
{

enum class invoke_it
template<class T, class TT>
using is_ntmocp_constructible = std::conditional_t<std::is_reference<TT>::value || !std::is_nothrow_move_constructible<TT>::value,
typename std::is_constructible<T, TT const &>::type,
typename std::is_constructible<T, TT>::type>;

template<class T, class TT>
constexpr auto is_nothrow_move_or_copy_constructible_from_v = is_ntmocp_constructible<T, TT>::value;


template<class T,
class U = std::conditional_t<(!std::is_nothrow_move_assignable<T>::value
&& std::is_copy_assignable<T>::value),
T const &,
T &&>>
constexpr U move_assign_if_noexcept(T& value) noexcept
{
once,
again
};
return std::move(value);
}


template<class Ressource, class Deleter>
class unique_resource_t
template<class R, class D>
class unique_resource
{
public:

explicit unique_resource_t(Ressource&& res, Deleter&& deleter, bool shouldrun = true) noexcept : m_resource(std::move(res)),
m_deleter(std::move(deleter)),
m_execute_on_destruction(shouldrun)
template<class RR, class DD,
std::enable_if_t<(!std::is_lvalue_reference<RR>::value)
&& std::is_nothrow_constructible<R, RR>::value, int> = 0,
std::enable_if_t<(!std::is_lvalue_reference<DD>::value)
&& std::is_nothrow_constructible<D, DD>::value, int> = 0,
std::enable_if_t<(std::is_copy_constructible<R>::value || std::is_nothrow_move_constructible<R>::value)
&& (std::is_copy_constructible<D>::value || std::is_nothrow_move_constructible<D>::value), int> = 0,
std::enable_if_t<is_nothrow_move_or_copy_constructible_from_v<R, RR>, int> = 0,
std::enable_if_t<is_nothrow_move_or_copy_constructible_from_v<D, DD>, int> = 0
>
explicit unique_resource(RR&& r, DD&& d) noexcept(std::is_nothrow_constructible<R,RR>::value
&& std::is_nothrow_constructible<D, DD>::value)
: m_resource(std::move(r)),
m_deleter(std::move(d)),
m_execute_on_destruction(true)
{
}

unique_resource_t(const unique_resource_t&) = delete;
template<class RR, class DD,
std::enable_if_t<std::is_lvalue_reference<RR>::value || std::is_lvalue_reference<DD>::value, int> = 0,
std::enable_if_t<(std::is_copy_constructible<R>::value || std::is_nothrow_move_constructible<R>::value)
&& (std::is_copy_constructible<D>::value || std::is_nothrow_move_constructible<D>::value), int> = 0,
std::enable_if_t<is_nothrow_move_or_copy_constructible_from_v<R, RR>, int> = 0,
std::enable_if_t<is_nothrow_move_or_copy_constructible_from_v<D, DD>, int> = 0
>
explicit unique_resource(RR&& r, DD&& d) noexcept(std::is_nothrow_constructible<R, RR>::value
&& std::is_nothrow_constructible<D, DD>::value)
try : m_resource(r),
m_deleter(d),
m_execute_on_destruction(true)
{
}
catch( ... )
{
d(r);
}

unique_resource_t(unique_resource_t&& other) noexcept : m_resource(std::move(other.m_resource)),
m_deleter(std::move(other.m_deleter)),
m_execute_on_destruction(other.m_execute_on_destruction)
template<class TR = R, class TD = D,
std::enable_if_t<(std::is_nothrow_move_constructible<TR>::value
&& std::is_nothrow_move_constructible<TD>::value), int> = 0
>
unique_resource(unique_resource&& other) noexcept(std::is_nothrow_move_constructible<R>::value
&& std::is_nothrow_move_constructible<D>::value)
: m_resource(std::forward<R>(other.m_resource)),
m_deleter(std::forward<D>(other.m_deleter)),
m_execute_on_destruction(std::exchange(other.m_execute_on_destruction, false))
{
other.release();
}

~unique_resource_t()
template<class TR = R, class TD = D,
std::enable_if_t<(!std::is_nothrow_move_constructible<TR>::value
|| !std::is_nothrow_move_constructible<TD>::value), int> = 0
>
unique_resource(unique_resource&& other) noexcept(std::is_nothrow_move_constructible<R>::value
&& std::is_nothrow_move_constructible<D>::value)
: m_resource(other.m_resource),
m_deleter(other.m_deleter),
m_execute_on_destruction(std::exchange(other.m_execute_on_destruction, false))
{
invoke(invoke_it::once);
}

unique_resource(const unique_resource&) = delete;

void invoke(const invoke_it strategy = invoke_it::once) noexcept
~unique_resource()
{
reset();
}


void reset()
{
if( m_execute_on_destruction == true )
{
call_deleter_safe();
m_execute_on_destruction = false;
get_deleter()(m_resource);
}

m_execute_on_destruction = ( strategy == invoke_it::again );
}

Ressource release() noexcept
template<class RR>
void reset(RR&& r)
{
m_execute_on_destruction = false;
return m_resource;
}
reset();

void reset(Ressource&& res) noexcept
{
invoke(invoke_it::again);
m_resource = std::move(res);
m_resource = move_assign_if_noexcept(r);
m_execute_on_destruction = true;
}

const Ressource& get() const noexcept
void release()
{
return m_resource;
m_execute_on_destruction = false;
}

operator const Ressource&() const noexcept
const R& get() const noexcept
{
return m_resource;
}

template<class R = Ressource,
std::enable_if_t<std::is_pointer<R>::value &&
( std::is_class<std::remove_pointer_t<R>>::value
|| std::is_union<std::remove_pointer_t<R>>::value ), int> = 0>
R operator->() const noexcept
template<class RR = R,
std::enable_if_t<std::is_pointer<RR>::value && std::is_nothrow_copy_constructible<RR>::value
&& ( std::is_class<std::remove_pointer_t<RR>>::value
|| std::is_union<std::remove_pointer_t<RR>>::value ), int> = 0
>
RR operator->() const noexcept
{
return m_resource;
}

template<class R = Ressource,
std::enable_if_t<std::is_pointer<R>::value, int> = 0>
std::add_lvalue_reference_t<std::remove_pointer_t<Ressource>> operator*() const noexcept
template<class RR = R,
std::enable_if_t<std::is_pointer<RR>::value, int> = 0>
std::add_lvalue_reference_t<std::remove_pointer_t<RR>> operator*() const noexcept
{
return *get();
}

const Deleter& get_deleter() const noexcept
const D& get_deleter() const noexcept
{
return m_deleter;
}

Deleter& get_deleter() noexcept
{
return m_deleter;
}

unique_resource_t& operator=(unique_resource_t&& other) noexcept
template<class RR = R, class DD = D,
std::enable_if_t<(std::is_nothrow_move_assignable<RR>::value || std::is_nothrow_copy_assignable<RR>::value)
&& (std::is_nothrow_copy_assignable<DD>::value || std::is_nothrow_copy_assignable<DD>::value), int> = 0
>
unique_resource& operator=(unique_resource&& other)
{
invoke(invoke_it::once);
m_resource = std::move(other.m_resource);
m_deleter = std::move(other.m_deleter);
m_execute_on_destruction = other.m_execute_on_destruction;
other.release();

if( this != &other )
{
reset();
m_resource = std::forward<RR>(other.m_resource);
m_deleter = std::forward<DD>(other.m_deleter);
m_execute_on_destruction = std::exchange(other.m_execute_on_destruction, false);
}
return *this;
}


unique_resource_t& operator=(const unique_resource_t&) = delete;
unique_resource& operator=(const unique_resource&) = delete;


private:

void call_deleter_safe() noexcept
{
try
{
get_deleter()(m_resource);
}
catch( ... ) { /* Empty */ }
}

Ressource m_resource;
Deleter m_deleter;
R m_resource;
D m_deleter;
bool m_execute_on_destruction;
};



template<class Ressource, class Deleter>
unique_resource_t<Ressource, Deleter> unique_resource(Ressource&& res, Deleter d) noexcept
template<class R, class D>
unique_resource<std::decay_t<R>, std::decay_t<D>> make_unique_resource(R&& r, D&& d)
noexcept(std::is_nothrow_constructible<std::decay_t<R>, R>::value
&& std::is_nothrow_constructible<std::decay_t<D>, D>::value)
{
return unique_resource<std::decay_t<R>, std::decay_t<D>>{std::forward<R>(r), std::forward<D>(d)};
}

template<class R, class D>
unique_resource<R&, std::decay_t<D>> make_unique_resource(std::reference_wrapper<R> r, D d)
noexcept(std::is_nothrow_constructible<std::decay_t<D>, D>::value)
{
return unique_resource_t<Ressource, Deleter>{std::move(res), std::move(d), true};
return unique_resource<R&, std::decay_t<D>>(r.get(), std::forward<D>(d));
}

template<class Ressource, class Deleter>
unique_resource_t<Ressource, Deleter> unique_resource_checked(Ressource res, Ressource invalid, Deleter d) noexcept
template<class R, class D, class S = R>
unique_resource<std::decay_t<R>, std::decay_t<D>> make_unique_resource_checked(R&& r, const S& invalid, D&& d)
noexcept(std::is_nothrow_constructible<std::decay_t<R>, R>::value
&& std::is_nothrow_constructible<std::decay_t<D>, D>::value)
{
return unique_resource_t<Ressource, Deleter>{std::move(res), std::move(d), (res != invalid)};
const bool mustRelease{r == invalid};
auto ur = make_unique_resource(r, d);

if( mustRelease == true )
{
ur.release();
}

return ur;
}

}

+ 170
- 128
test/UniqueResourceTest.cpp Bestand weergeven

@@ -19,7 +19,6 @@
*/

#include "unique_resource.h"
#include <functional>
#include <catch.hpp>
#include <trompeloeil.hpp>

@@ -36,211 +35,254 @@ namespace
MAKE_MOCK1(deleter, void(Handle));
};


struct ThrowOnCopyMock
{
ThrowOnCopyMock() { }

ThrowOnCopyMock(const ThrowOnCopyMock&)
{
throw std::exception{};
}

MAKE_CONST_MOCK1(deleter, void(ThrowOnCopyMock));

ThrowOnCopyMock& operator=(const ThrowOnCopyMock&)
{
throw std::exception{};
}
};

struct NotNothrowMoveMock
{
NotNothrowMoveMock(CallMock* mo) : m_mock(mo) { }
NotNothrowMoveMock(const NotNothrowMoveMock& other) : m_mock(other.m_mock) { }
NotNothrowMoveMock(NotNothrowMoveMock&& other) noexcept(false) : m_mock(other.m_mock) { }

void operator()(Handle h) const
{
m_mock->deleter(h);
}

NotNothrowMoveMock& operator=(const NotNothrowMoveMock&)
{
throw "Not implemented";
}

NotNothrowMoveMock& operator=(NotNothrowMoveMock&&)
{
throw "Not implemented";
}

CallMock* m_mock;

};

struct ConditialThrowOnCopyMock
{
explicit ConditialThrowOnCopyMock(Handle h, bool shouldThrow) : m_handle(h), m_shouldThrow(shouldThrow)
{
}

ConditialThrowOnCopyMock(const ConditialThrowOnCopyMock& other) : m_handle(other.m_handle), m_shouldThrow(other.m_shouldThrow)
{
if( m_shouldThrow == true )
{
throw std::exception{};
}
}

ConditialThrowOnCopyMock(ConditialThrowOnCopyMock&&) = default;

ConditialThrowOnCopyMock& operator=(const ConditialThrowOnCopyMock& other)
{
if( &other != this )
{
m_handle = other.m_handle;
m_shouldThrow = other.m_shouldThrow;

if( m_shouldThrow == true )
{
throw std::exception{};
}
}

return *this;
}

ConditialThrowOnCopyMock& operator=(ConditialThrowOnCopyMock&&) = default;

Handle m_handle;
bool m_shouldThrow;
};


CallMock m;

void deleter(Handle h)
{
m.deleter(h);
}
}


TEST_CASE("deleter called on destruction", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3));

auto guard = sr::unique_resource(Handle{3}, deleter);
static_cast<void>(guard);
}

TEST_CASE("deleter is not called if released", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3)).TIMES(0);
auto guard = sr::unique_resource(Handle{3}, deleter);
guard.release();
}

TEST_CASE("deleter called if checked valid", "[UniqueResource]")
TEST_CASE("construction with move", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3));
auto guard = sr::unique_resource_checked(Handle{3}, Handle{6}, deleter);
auto guard = sr::make_unique_resource(Handle{3}, deleter);
static_cast<void>(guard);
}

TEST_CASE("deleter not called if checked invalid", "[UniqueResource]")
TEST_CASE("construction with copy", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3)).TIMES(0);
auto guard = sr::unique_resource_checked(Handle{3}, Handle{3}, deleter);
REQUIRE_CALL(m, deleter(3));
const Handle h{3};
const auto d = [](auto v) { m.deleter(v); };
auto guard = sr::make_unique_resource(h, d);
static_cast<void>(guard);
}

TEST_CASE("release returns reference to resource", "[UniqueResource]")
TEST_CASE("construction with copy calls deleter and rethrows on failed copy", "[UniqueResource]")
{
auto guard = sr::unique_resource(Handle{3}, [](auto) { });
const auto result = guard.release();
REQUIRE_THROWS([] {
const ThrowOnCopyMock noMove;
const auto d = [](const auto&) { m.deleter(3); };
REQUIRE_CALL(m, deleter(3));

REQUIRE(3 == result);
}

TEST_CASE("move releases moved-from object", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3));
auto movedFrom = sr::unique_resource(Handle{3}, deleter);
auto guard = std::move(movedFrom);
static_cast<void>(guard);
sr::unique_resource<decltype(noMove), decltype(d)> guard{noMove, d};
static_cast<void>(guard);
}());
}

TEST_CASE("move transfers state", "[UniqueResource]")
TEST_CASE("move-construction with move", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3));
auto movedFrom = sr::unique_resource(Handle{3}, deleter);
auto movedFrom = sr::make_unique_resource(Handle{3}, deleter);
auto guard = std::move(movedFrom);
static_cast<void>(guard);
CHECK(guard.get() == 3);
}

TEST_CASE("move transfers state if released", "[UniqueResource]")
TEST_CASE("move-construction with copy", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(ANY(Handle))).TIMES(0); // TODO: Use ANY(T)

auto movedFrom = sr::unique_resource(Handle{3}, deleter);
movedFrom.release();
CallMock mock;
REQUIRE_CALL(mock, deleter(3));
const NotNothrowMoveMock notNothrow{&mock};
Handle h{3};
sr::unique_resource<Handle, decltype(notNothrow)> movedFrom{h, notNothrow};
auto guard = std::move(movedFrom);
static_cast<void>(guard);
CHECK(guard.get() == 3);
}

TEST_CASE("move assignment releases moved-from object", "[UniqueResource]")
TEST_CASE("move assignment calls deleter", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(5));
REQUIRE_CALL(m, deleter(3));
auto moveFrom = sr::make_unique_resource(Handle{3}, deleter);
REQUIRE_CALL(m, deleter(4));

auto movedFrom = sr::unique_resource(Handle{3}, deleter);
auto guard = sr::unique_resource(Handle{5}, deleter);
guard = std::move(movedFrom);
static_cast<void>(guard);
{
REQUIRE_CALL(m, deleter(3));
auto guard = sr::make_unique_resource(Handle{4}, deleter);
guard = std::move(moveFrom);
}
}

TEST_CASE("move assignment transfers state", "[UniqueResource]")
TEST_CASE("deleter called on destruction", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(5));
REQUIRE_CALL(m, deleter(3));

auto movedFrom = sr::unique_resource(Handle{3}, deleter);
auto guard = sr::unique_resource(Handle{5}, deleter);
guard = std::move(movedFrom);
auto guard = sr::make_unique_resource(Handle{3}, deleter);
static_cast<void>(guard);
}

TEST_CASE("move assignment transfers state if released", "[UniqueResource]")
TEST_CASE("reset calls deleter", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(5));
auto guard = sr::make_unique_resource(Handle{3}, deleter);

auto movedFrom = sr::unique_resource(Handle{3}, deleter);
movedFrom.release();
auto guard = sr::unique_resource(Handle{5}, deleter);
guard = std::move(movedFrom);
static_cast<void>(guard);
{
REQUIRE_CALL(m, deleter(3));
guard.reset();
}
}

TEST_CASE("no exception propagation from deleter", "[UniqueResource]")
TEST_CASE("reset does not call deleter if released", "[UniqueResource]")
{
REQUIRE_NOTHROW([] {
auto guard = sr::unique_resource(Handle{3}, [](auto) { throw "Don't propagate this!"; });
static_cast<void>(guard);
}());
REQUIRE_CALL(m, deleter(3)).TIMES(0);
auto guard = sr::make_unique_resource(Handle{3}, deleter);
guard.release();
guard.reset();
}

TEST_CASE("invoke executes deleter on resource", "[UniqueResource]")
TEST_CASE("reset sets new value and calls deleter on previous", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3));

auto guard = sr::unique_resource(Handle{3}, deleter);
guard.invoke();
}

TEST_CASE("invoke executes only multiple times if again strategy", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3)).TIMES(3);

auto guard = sr::unique_resource(Handle{3}, deleter);
guard.invoke(sr::invoke_it::again);
guard.invoke(sr::invoke_it::again);
REQUIRE_CALL(m, deleter(7));
auto guard = sr::make_unique_resource(Handle{3}, deleter);
guard.reset(Handle{7});
}

TEST_CASE("invoke does nothing if released", "[UniqueResource]")
TEST_CASE("release disables deleter", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(ANY(Handle))).TIMES(0);

auto guard = sr::unique_resource(Handle{3}, deleter);
REQUIRE_CALL(m, deleter(3)).TIMES(0);
auto guard = sr::make_unique_resource(Handle{3}, deleter);
guard.release();
guard.invoke(sr::invoke_it::once);
}

TEST_CASE("invoke executes after release if again strategy", "[UniqueResource]")
TEST_CASE("get returns resource", "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3));

auto guard = sr::unique_resource(Handle{3}, deleter);
guard.release();
guard.invoke(sr::invoke_it::again);
auto guard = sr::make_unique_resource(Handle{3}, deleter);
CHECK(guard.get() == 3);
}

TEST_CASE("invoke does not propagate exception", "[UniqueResource]")
TEST_CASE("pointer access returns resource" "[UniqueResource]")
{
auto guard = sr::unique_resource(Handle{3}, [](auto) { throw "Don't propagate this!"; });
REQUIRE_NOTHROW(guard.invoke());
const auto p = std::make_pair(3, 4);
auto guard = sr::make_unique_resource(&p, [](auto*) { });
REQUIRE(guard->first == 3);
REQUIRE(guard->second == 4);
}

TEST_CASE("reset releases old ressource", "[UniqueResource]")
TEST_CASE("pointer dereference returns resource" "[UniqueResource]")
{
REQUIRE_CALL(m, deleter(3));
REQUIRE_CALL(m, deleter(7));

auto guard = sr::unique_resource(Handle{3}, deleter);
guard.reset(Handle{7});
Handle h{5};
auto guard = sr::make_unique_resource(PtrHandle{&h}, [](auto*) { });
REQUIRE(*guard == 5);
}

TEST_CASE("reset sets ressource", "[UniqueResource]")
TEST_CASE("deleter access", "[UniqueResource]")
{
auto guard = sr::unique_resource(Handle{3}, [](auto) { });
guard.reset(Handle{7});
REQUIRE(guard.get() == 7);
}
auto guard = sr::make_unique_resource(Handle{3}, deleter);
guard.release();

TEST_CASE("get accesses ressource", "[UniqueResource]")
{
auto guard = sr::unique_resource(Handle{3}, [](auto) { });
REQUIRE(guard.get() == 3);
{
REQUIRE_CALL(m, deleter(8));
guard.get_deleter()(8);
}
}

TEST_CASE("conversion operator", "[UniqueResource]")
TEST_CASE("make unique resource", "[UniqueResource]")
{
auto guard = sr::unique_resource(Handle{3}, [](auto) { });
const auto& ref = guard;
REQUIRE(ref == 3);
REQUIRE_CALL(m, deleter(7));
auto guard = sr::make_unique_resource(Handle{7}, deleter);
static_cast<void>(guard);
}

TEST_CASE("pointer access operator" "[UniqueResource]")
TEST_CASE("make unique resource with reference wrapper", "[UniqueResource]")
{
const auto p = std::make_pair(3, 4);
auto guard = sr::unique_resource(&p, [](auto) { });
const auto x = guard.operator->();
REQUIRE(x->first == 3);
REQUIRE_CALL(m, deleter(3));
Handle h{3};
auto guard = sr::make_unique_resource(std::ref(h), deleter);
static_cast<void>(guard);
}

TEST_CASE("dereference operator", "[UniqueResource]")
TEST_CASE("make unique resource checked", "[UniqueResource]")
{
Handle h{4};
auto guard = sr::unique_resource(PtrHandle{&h}, [](auto) { });
const auto x = guard.operator*();
REQUIRE(x == 4);
REQUIRE_CALL(m, deleter(4));
auto guard = sr::make_unique_resource_checked(Handle{4}, Handle{-1}, deleter);
static_cast<void>(guard);
}

TEST_CASE("deleter access", "[UniqueResource]")
TEST_CASE("make unique resource checked releases if invalid", "[UniqueResource]")
{
std::size_t value{0};
auto guard = sr::unique_resource(Handle{3}, [&value](auto v) { value = v; });
REQUIRE(value == 0);
guard.get_deleter()(6);
REQUIRE(value == 6);
auto guard = sr::make_unique_resource_checked(Handle{-1}, Handle{-1}, deleter);
static_cast<void>(guard);
}


Laden…
Annuleren
Opslaan