ソースを参照

Merge branch 'dev/unique_resource' into development

main
offa 7年前
コミット
33f2dbd0d3
2個のファイルの変更309行の追加198行の削除
  1. +139
    -70
      include/unique_resource.h
  2. +170
    -128
      test/UniqueResourceTest.cpp

+ 139
- 70
include/unique_resource.h ファイルの表示

namespace sr 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: 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 ) 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; 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; 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(); return *get();
} }


const Deleter& get_deleter() const noexcept
const D& get_deleter() const noexcept
{ {
return m_deleter; 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; return *this;
} }



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




private: 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; 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 ファイルの表示

*/ */


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


MAKE_MOCK1(deleter, void(Handle)); 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; CallMock m;


void deleter(Handle h) void deleter(Handle h)
{ {
m.deleter(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)); 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); 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); 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)); 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); 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); 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)); 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); 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)); 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.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)); 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);
} }



読み込み中…
キャンセル
保存