一段时间以来,我一直想在C++中使用延迟语句或C++,但直到最近,我一直认为C++没有它。然而,那时我发现了std::experimental::scope_exit
,但是它有一个问题,那就是它没有在我使用的任何编译器中实现。因此,我决定实施它。
scope.hh
#ifndef SCOPE_SCOPE_HH
#define SCOPE_SCOPE_HH
#include <utility>
#include <new>
#include <concepts>
namespace turtle
{
template<typename EF> requires std::invocable<EF> && requires(EF x) {{ x() } -> std::same_as<void>; }
struct scope_exit
{
constexpr scope_exit operator=(const scope_exit &) = delete;
constexpr scope_exit operator=(scope_exit &&) = delete;
template<typename Fn, typename =
std::enable_if_t<!std::is_same_v<std::remove_cvref_t<Fn>, scope_exit>, int>,
typename = std::enable_if_t<std::is_constructible_v<EF, Fn>, int>>
constexpr explicit scope_exit(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
std::is_nothrow_constructible_v<EF, Fn &>)
{
set_functor(std::forward<Fn>(fn));
}
template<typename = std::disjunction<std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>,
std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>>>
constexpr scope_exit(scope_exit &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>)
{
set_functor_move(std::move(other));
}
constexpr scope_exit(const scope_exit &) = delete;
constexpr ~scope_exit() noexcept
{
/* if active, call functor and then destroy it */
if (!m_released) {
m_functor();
m_released = true;
}
m_functor.~EF();
}
constexpr void release() noexcept
{
m_released = true;
}
constexpr const auto& exit_function() noexcept
{
return m_functor;
}
private:
union
{
EF m_functor;
char m_functor_bytes[sizeof(EF)] = {};
};
bool m_released{false};
template<typename Fn>
constexpr void set_functor(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
std::is_nothrow_constructible_v<EF, Fn &>)
{
if constexpr(!std::is_lvalue_reference_v<Fn> && std::is_nothrow_constructible_v<EF, Fn>) {
::new(&m_functor) EF(std::forward<Fn>(fn));
} else {
try {
::new(&m_functor) EF(fn);
} catch (...) {
m_released = true;
fn();
throw;
}
}
}
constexpr void set_functor_move(scope_exit &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>)
{
/* only preform construction if other is active */
if (!other.m_released) {
if constexpr(std::is_nothrow_move_constructible_v<EF>) {
::new(&m_functor) EF(std::forward<EF>(other.m_functor));
} else {
try {
::new(&m_functor) EF(other.m_functor);
} catch (...) {
m_released = true;
other.m_functor();
other.release();
throw;
}
}
other.release();
}
}
};
template<typename EF>
scope_exit(EF) -> scope_exit<EF>;
}
namespace turtle
{
template<typename EF> requires std::invocable<EF> && requires(EF x) {{ x() } -> std::same_as<void>; }
struct scope_fail
{
constexpr scope_fail operator=(const scope_fail &) = delete;
constexpr scope_fail operator=(scope_fail &&) = delete;
template<typename Fn, typename =
std::enable_if_t<!std::is_same_v<std::remove_cvref_t<Fn>, scope_fail>, int>,
typename = std::enable_if_t<std::is_constructible_v<EF, Fn>, int>>
constexpr explicit scope_fail(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
std::is_nothrow_constructible_v<EF, Fn &>)
: m_uncaught_exceptions(std::uncaught_exceptions())
{
set_functor(std::forward<Fn>(fn));
}
template<typename = std::disjunction<std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>,
std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>>>
constexpr scope_fail(scope_fail &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>)
: m_uncaught_exceptions(other.m_uncaught_exceptions)
{
set_functor_move(std::move(other));
}
constexpr scope_fail(const scope_fail &) = delete;
constexpr ~scope_fail() noexcept
{
/* if active and an exception happened, call functor. */
if (!m_released && std::uncaught_exceptions() > m_uncaught_exceptions) {
m_functor();
}
/* destroy functor */
m_functor.~EF();
}
constexpr void release() noexcept
{
m_released = true;
}
constexpr const auto& exit_function() noexcept
{
return m_functor;
}
private:
union
{
EF m_functor;
char m_functor_bytes[sizeof(EF)] = {};
};
bool m_released{false};
int m_uncaught_exceptions{0};
template<typename Fn>
constexpr void set_functor(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
std::is_nothrow_constructible_v<EF, Fn &>)
{
if constexpr(!std::is_lvalue_reference_v<Fn> && std::is_nothrow_constructible_v<EF, Fn>) {
::new(&m_functor) EF(std::forward<Fn>(fn));
} else {
try {
::new(&m_functor) EF(fn);
} catch (...) {
m_released = true;
fn();
throw;
}
}
}
constexpr void set_functor_move(scope_fail &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>)
{
/* only preform construction if other is active */
if (!other.m_released) {
if constexpr(std::is_nothrow_move_constructible_v<EF>) {
::new(&m_functor) EF(std::forward<EF>(other.m_functor));
} else {
try {
::new(&m_functor) EF(other.m_functor);
} catch (...) {
m_released = true;
other.m_functor();
other.release();
throw;
}
}
other.release();
}
}
};
template<typename EF>
scope_fail(EF) -> scope_fail<EF>;
}
namespace turtle
{
template<typename EF> requires std::invocable<EF> && requires(EF x) {{ x() } -> std::same_as<void>; }
struct scope_success
{
constexpr scope_success operator=(const scope_success &) = delete;
constexpr scope_success operator=(scope_success &&) = delete;
template<typename Fn, typename =
std::enable_if_t<!std::is_same_v<std::remove_cvref_t<Fn>, scope_success>, int>,
typename = std::enable_if_t<std::is_constructible_v<EF, Fn>, int>>
constexpr explicit scope_success(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
std::is_nothrow_constructible_v<EF, Fn &>)
: m_uncaught_exceptions(std::uncaught_exceptions())
{
set_functor(std::forward<Fn>(fn));
}
template<typename = std::disjunction<std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>,
std::enable_if_t<std::is_nothrow_move_constructible_v<EF>, int>>>
constexpr scope_success(scope_success &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>)
: m_uncaught_exceptions(other.m_uncaught_exceptions)
{
set_functor_move(std::move(other));
}
constexpr scope_success(const scope_success &) = delete;
constexpr ~scope_success() noexcept(noexcept(std::declval<EF&>()()))
{
/* if active and an exception did not happen, call functor. */
if (!m_released && std::uncaught_exceptions() <= m_uncaught_exceptions) {
m_functor();
}
/* destroy functor */
m_functor.~EF();
}
constexpr void release() noexcept
{
m_released = true;
}
constexpr const auto& exit_function() noexcept
{
return m_functor;
}
private:
union
{
EF m_functor;
char m_functor_bytes[sizeof(EF)] = {};
};
bool m_released{false};
int m_uncaught_exceptions{0};
template<typename Fn>
constexpr void set_functor(Fn &&fn) noexcept(std::is_nothrow_constructible_v<EF, Fn> ||
std::is_nothrow_constructible_v<EF, Fn &>)
{
if constexpr(!std::is_lvalue_reference_v<Fn> && std::is_nothrow_constructible_v<EF, Fn>) {
::new(&m_functor) EF(std::forward<Fn>(fn));
} else {
try {
::new(&m_functor) EF(fn);
} catch (...) {
m_released = true;
fn();
throw;
}
}
}
constexpr void set_functor_move(scope_success &&other) noexcept(std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>)
{
/* only preform construction if other is active */
if (!other.m_released) {
if constexpr(std::is_nothrow_move_constructible_v<EF>) {
::new(&m_functor) EF(std::forward<EF>(other.m_functor));
} else {
try {
::new(&m_functor) EF(other.m_functor);
} catch (...) {
m_released = true;
other.m_functor();
other.release();
throw;
}
}
other.release();
}
}
};
template<typename EF>
scope_success(EF) -> scope_success<EF>;
}
#endif //SCOPE_SCOPE_HH
以下是其中的一个例子:
main.cc
#include <iostream>
#include <ctime>
#include <SDL.h>
#include "scope.hh"
int main(int, char*[])
{
// Reseed rand
std::srand(std::time(0));
SDL_Init(SDL_INIT_VIDEO); // Initialize SDL2
// Create an application window with the following settings:
SDL_Window* window = SDL_CreateWindow(
"An SDL2 window", // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
640, // width, in pixels
480, // height, in pixels
SDL_WINDOW_OPENGL
);
// Check that the window was successfully created
if (window == nullptr)
{
// In the case that the window could not be made...
std::cerr << "Could not create window: " << SDL_GetError() << "\n";
return 1;
}
// Clean up when exiting the scope
turtle::scope_exit cleanup{[&]{
SDL_DestroyWindow(window);
SDL_Quit();
}};
// An exception could be thrown
if(std::rand() % 4 == 0)
{
throw;
}
// The window is open: could enter program loop here
SDL_Delay(3000); // Pause execution for 3000 milliseconds, for example
return 0;
}
发布于 2020-07-24 07:04:19
没有必要要求x()
完全返回void
-实际上,任何返回类型都可以,因为我们可以放弃结果。因此,对struct
s的要求应该简化为template <std::invocable EF>
。
此代码存在问题:
std::disjunction,int>,std::enable_if_t,int>>>
std::disjunction
不像你想的那样工作;move
s应该是copy
;std::enable_if_t
的SFINAE需要依赖于函数模板的模板参数。所以应该是
template <typename EF2 = EF, typename = std::enable_if_t<
std::is_nothrow_move_constructible_v<EF2>
|| std::is_nothrow_copy_constructible_v<EF2>
>>
或者只使用requires
。
你需要特例推荐信。union
s包含参考资料是格式不正确的IIRC。
不要将事情标记为constexpr
,除非规范这么说,如果您的目标是一致性--添加constexpr
的标准禁止实现。
如前所述,在许多情况下,您可以使用requires
而不是enable_if
。
您不需要使用union
-simulated std::optional
来存储退出函数,因为所有的范围保护(包括非活动的)都会保持其退出函数的活力。(每个注释)对于移动构造函数,使用std::move_if_noexcept
进行noexcept
分派行为;例如:
scope_exit(scope_exit&& other)
noexcept(std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>)
requires std::is_nothrow_move_constructible_v<EF> ||
std::is_copy_constructible_v<EF>
: m_functor(std::move_if_noexcept(other.m_functor))
{
other.release();
}
https://codereview.stackexchange.com/questions/245846
复制相似问题