I want to use the scope_exit
class but my compiler/standard library (clang++-16 with libc++) don't support it. Until they do I wanted an implementation. I found a few on the Internet but they weren't quite what I wanted. So I made an attempt at implementing it.
I used requires
clauses on the constructor overloads to implement the forwarding behavior described on the cppreference.com pages.
I came up with a brief suite of test objects (see test_scope_exit.cpp below) but it's paltry; what are more test cases where this should work? What are some examples of when it should not work so I can check if the compiler gives good diagnostic messages?
How could the code be improved for clarity, idiosyncrasy, etc?
scope_exit.hpp:
#ifndef SCOPE_EXIT
#define SCOPE_EXIT
#include <type_traits>
#include <utility>
template <typename EF> class Scope_Exit {
private:
template <typename Fn>
static inline constexpr bool noexcept_ctor =
std::is_nothrow_constructible_v<EF, Fn> ||
std::is_nothrow_constructible_v<EF, Fn &>;
static inline constexpr bool noexcept_move =
std::is_nothrow_move_constructible_v<EF> ||
std::is_nothrow_copy_constructible_v<EF>;
public:
template <typename Fn>
requires(!std::is_lvalue_reference_v<Fn> &&
std::is_nothrow_constructible_v<EF, Fn>)
explicit Scope_Exit(Fn &&fn) noexcept(noexcept_ctor<Fn>)
: exitfun(std::forward<Fn>(fn)) {}
template <typename Fn>
explicit Scope_Exit(Fn &&fn) noexcept(noexcept_ctor<Fn>) : exitfun(fn) {}
Scope_Exit(Scope_Exit &&other) noexcept(noexcept_move)
requires std::is_nothrow_move_constructible_v<EF>
: active(other.active), exitfun(std::forward<EF>(other.exitfun)) {
other.release();
}
Scope_Exit(Scope_Exit &&other) noexcept(noexcept_move)
: active(other.active), exitfun(other.exitfun) {
other.release();
}
Scope_Exit(const Scope_Exit &) = delete;
Scope_Exit &operator=(Scope_Exit &&) = delete;
Scope_Exit &operator=(const Scope_Exit &) = delete;
~Scope_Exit() noexcept {
if (active)
exitfun();
}
void release() noexcept { active = false; }
private:
bool active = true;
EF exitfun;
};
template <class EF> Scope_Exit(EF) -> Scope_Exit<EF>;
#endif // SCOPE_EXIT
test_scope_exit.cpp:
#include "../src/scope_exit.hpp"
#include <iostream>
#include <utility>
#include <functional>
void foo() {
std::cout << "foo()" << std::endl;
}
void bar(int x) {
std::cout << "bar() => " << x << std::endl;
}
class Baz {
public:
Baz() = default;
Baz(const Baz &other) { std::cout << "copied Baz" << std::endl; }
void operator()() { std::cout << "Baz::operator()" << std::endl; }
};
int main() {
Scope_Exit se1(foo);
Scope_Exit se2(&foo);
void (&foo_ref)() = foo;
Scope_Exit se3(foo_ref);
Scope_Exit se4([]{ std::cout << "lambda" << std::endl; });
Scope_Exit se5(std::bind(bar, 2));
int y = 5;
Scope_Exit se6([&]{ std::cout << "capturing lambda => " << y << std::endl; });
Baz baz;
Scope_Exit se7(baz);
Baz &baz_ref = baz;
Scope_Exit se8(baz_ref);
Scope_Exit<Baz &> se9(baz_ref); // explicitly specifying a reference type prevents copy
return 0;
}
result:
copied Baz
copied Baz
Baz::operator()
Baz::operator()
Baz::operator()
capturing lambda => 5
bar() => 2
lambda
foo()
foo()
foo()