mirror of
https://github.com/davisking/dlib.git
synced 2024-11-01 10:14:53 +08:00
[scope] Adding scope_exit (#2875)
* initial commit * is this enough? * is this enough docs? I'm not great at writing docs --------- Co-authored-by: pf <pf@me> Co-authored-by: pf <pf@pf>
This commit is contained in:
parent
afede571cb
commit
3624bf9f05
89
dlib/scope.h
Normal file
89
dlib/scope.h
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (C) 2023 Davis E. King (davis@dlib.net)
|
||||||
|
// License: Boost Software License See LICENSE.txt for the full license.
|
||||||
|
#ifndef DLIB_SCOPE_H_
|
||||||
|
#define DLIB_SCOPE_H_
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <functional>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace dlib
|
||||||
|
{
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
template<class Fn>
|
||||||
|
class scope_exit
|
||||||
|
{
|
||||||
|
/*!
|
||||||
|
WHAT THIS OBJECT REPRESENTS
|
||||||
|
This is a standard's compliant backport of std::experimental::scope_exit that works with C++14.
|
||||||
|
|
||||||
|
Therefore, refer to https://en.cppreference.com/w/cpp/experimental/scope_exit for docs on the
|
||||||
|
interface of scope_exit.
|
||||||
|
!*/
|
||||||
|
|
||||||
|
private:
|
||||||
|
Fn f_;
|
||||||
|
bool active_{true};
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr scope_exit() = delete;
|
||||||
|
constexpr scope_exit(const scope_exit &) = delete;
|
||||||
|
constexpr scope_exit &operator=(const scope_exit &) = delete;
|
||||||
|
constexpr scope_exit &operator=(scope_exit &&) = delete;
|
||||||
|
|
||||||
|
constexpr scope_exit(scope_exit &&other) noexcept(std::is_nothrow_move_constructible<Fn>::value)
|
||||||
|
: f_{std::move(other.f_)}, active_{std::exchange(other.active_, false)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<
|
||||||
|
class F,
|
||||||
|
std::enable_if_t<!std::is_same<std::decay_t<F>, scope_exit>::value, bool> = true
|
||||||
|
>
|
||||||
|
explicit scope_exit(F&& f) noexcept(std::is_nothrow_constructible<Fn,F>::value)
|
||||||
|
: f_{std::forward<F>(f)}, active_{true}
|
||||||
|
{}
|
||||||
|
|
||||||
|
~scope_exit() noexcept
|
||||||
|
{
|
||||||
|
if (active_)
|
||||||
|
f_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void release() noexcept { active_ = false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Fn>
|
||||||
|
auto make_scope_exit(Fn&& f)
|
||||||
|
/*!
|
||||||
|
ensures:
|
||||||
|
- This is factory function that wraps the callback in a scope_exit object.
|
||||||
|
!*/
|
||||||
|
|
||||||
|
{
|
||||||
|
return scope_exit<std::decay_t<Fn>>(std::forward<Fn>(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cpp_deduction_guides
|
||||||
|
template<class Fn>
|
||||||
|
scope_exit(Fn) -> scope_exit<Fn>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
using scope_exit_erased = scope_exit<std::function<void()>>;
|
||||||
|
/*!
|
||||||
|
WHAT THIS OBJECT REPRESENTS
|
||||||
|
This is a type erased version of scope_exit. I.e. there is no template parameter.
|
||||||
|
Use this object if you wish to hide the exact function signature, for example
|
||||||
|
if splitting a declaration and definition across a header file and cpp file.
|
||||||
|
This does come at a slight performance penalty since it may incur a heap allocation
|
||||||
|
and due to a pointer indirection, the compiler may not inline your callback.
|
||||||
|
!*/
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //DLIB_SCOPE_H_
|
@ -162,6 +162,7 @@ add_executable(${target_name} main.cpp tester.cpp
|
|||||||
te.cpp
|
te.cpp
|
||||||
ffmpeg.cpp
|
ffmpeg.cpp
|
||||||
optional.cpp
|
optional.cpp
|
||||||
|
scope.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
get_filename_component(DLIB_FFMPEG_DATA ${CMAKE_SOURCE_DIR}/ffmpeg_data/details.cfg REALPATH)
|
get_filename_component(DLIB_FFMPEG_DATA ${CMAKE_SOURCE_DIR}/ffmpeg_data/details.cfg REALPATH)
|
||||||
|
171
dlib/test/scope.cpp
Normal file
171
dlib/test/scope.cpp
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (C) 2023 Davis E. King (davis@dlib.net)
|
||||||
|
// License: Boost Software License See LICENSE.txt for the full license.
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <dlib/scope.h>
|
||||||
|
#include "tester.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
using namespace test;
|
||||||
|
using namespace dlib;
|
||||||
|
|
||||||
|
logger dlog("test.scope");
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void test_scope_exit()
|
||||||
|
{
|
||||||
|
int counter{0};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto s1 = make_scope_exit([&]{++counter;});
|
||||||
|
static_assert(!std::is_copy_constructible<decltype(s1)>::value, "bad");
|
||||||
|
static_assert(!std::is_copy_assignable<decltype(s1)>::value, "bad");
|
||||||
|
static_assert(!std::is_move_assignable<decltype(s1)>::value, "bad");
|
||||||
|
static_assert(std::is_move_constructible<decltype(s1)>::value, "bad");
|
||||||
|
auto s2 = std::move(s1);
|
||||||
|
auto s3 = std::move(s2);
|
||||||
|
auto s4 = std::move(s3);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLIB_TEST(counter == 1);
|
||||||
|
|
||||||
|
const auto fn_inner = [&]
|
||||||
|
{
|
||||||
|
auto s = make_scope_exit([&]{++counter;});
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto fn_outer = [&]
|
||||||
|
{
|
||||||
|
auto s = fn_inner();
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto s = fn_outer();
|
||||||
|
}
|
||||||
|
|
||||||
|
DLIB_TEST(counter == 2);
|
||||||
|
|
||||||
|
#ifdef __cpp_deduction_guides
|
||||||
|
|
||||||
|
{
|
||||||
|
scope_exit s{[&]{++counter;}};
|
||||||
|
}
|
||||||
|
|
||||||
|
DLIB_TEST(counter == 3);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
void test_scope_exit_erased()
|
||||||
|
{
|
||||||
|
int counter{0};
|
||||||
|
|
||||||
|
{
|
||||||
|
scope_exit_erased s1([&]{++counter;});
|
||||||
|
static_assert(!std::is_copy_constructible<decltype(s1)>::value, "bad");
|
||||||
|
static_assert(!std::is_copy_assignable<decltype(s1)>::value, "bad");
|
||||||
|
static_assert(!std::is_move_assignable<decltype(s1)>::value, "bad");
|
||||||
|
static_assert(std::is_move_constructible<decltype(s1)>::value, "bad");
|
||||||
|
auto s2 = std::move(s1);
|
||||||
|
auto s3 = std::move(s2);
|
||||||
|
auto s4 = std::move(s3);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLIB_TEST(counter == 1);
|
||||||
|
|
||||||
|
const auto fn_inner = [&]
|
||||||
|
{
|
||||||
|
scope_exit_erased s([&]{++counter;});
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto fn_outer = [&]
|
||||||
|
{
|
||||||
|
auto s = fn_inner();
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto s = fn_outer();
|
||||||
|
}
|
||||||
|
|
||||||
|
DLIB_TEST(counter == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct results_with_delayed_C_library_resource_management
|
||||||
|
{
|
||||||
|
int ndata{0};
|
||||||
|
char* data{nullptr};
|
||||||
|
scope_exit_erased s;
|
||||||
|
};
|
||||||
|
|
||||||
|
void test_composition()
|
||||||
|
{
|
||||||
|
int counter{0};
|
||||||
|
|
||||||
|
const auto fn = [&]
|
||||||
|
{
|
||||||
|
// Pretend you're in a cpp file using a C library which isn't exposed to the API via the header.
|
||||||
|
// You want to return some results, but those results are only valid so long as something returned by the C library is still alive
|
||||||
|
// You want to delay releasing any resources allocated by the C library until after you've returned your results and the caller is done using them.
|
||||||
|
// You could return a std::unique_ptr<results> object with a custom deleter which deletes that resource but because all types in std::unique_ptr
|
||||||
|
// must be complete types, you would have to pollute the header. You can use std::shared_ptr with a custom deleter, defined at runtime,
|
||||||
|
// but this is less efficient.
|
||||||
|
// You can use a scope_exit_erased object to wrap the resouce management function from the C library and delay the call further up the stack,
|
||||||
|
// all behind a type erased callback.
|
||||||
|
|
||||||
|
// pretend malloc() is a fancy function from some exotic C library.
|
||||||
|
// pretend free() is another fancy function which you don't want users to have to manually call, and you want to delay calling it until after the results are used
|
||||||
|
// pretend cstdlib is a fancy header you don't want to expose in your own header file.
|
||||||
|
char* data = (char*)std::malloc(100);
|
||||||
|
std::memset(data, 0, 100);
|
||||||
|
std::snprintf(data, 100, "hello there!");
|
||||||
|
scope_exit_erased s{[=, &counter] {free(data); ++counter;}};
|
||||||
|
|
||||||
|
results_with_delayed_C_library_resource_management results{100, data, std::move(s)};
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
// Oh, look at me. I'm using these results, blissfully unaware that some super complicated function in a C library will get called when i'm done using results.
|
||||||
|
const auto results = fn();
|
||||||
|
DLIB_TEST(results.ndata == 100);
|
||||||
|
DLIB_TEST(std::strcmp(results.data, "hello there!") == 0);
|
||||||
|
DLIB_TEST(counter == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLIB_TEST(counter == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class scope_tester : public tester
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
scope_tester (
|
||||||
|
) :
|
||||||
|
tester ("test_scope",
|
||||||
|
"Runs tests on the scope_exit and related objects")
|
||||||
|
{}
|
||||||
|
|
||||||
|
void perform_test (
|
||||||
|
)
|
||||||
|
{
|
||||||
|
test_scope_exit();
|
||||||
|
test_scope_exit_erased();
|
||||||
|
test_composition();
|
||||||
|
}
|
||||||
|
} a;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user