Recently I tried to reinvent scope guard via std::unique_ptr
(NOTE: Deleter has the member typedef pointer
— is a specially handled case of std::unique_ptr
):
#include <type_traits>
#include <utility>
#include <memory>
#include <iostream>
#include <cstdlib>
#include <cassert>
namespace
{
template< typename lambda >
auto
make_scope_guard(lambda && _lambda)
{
struct lambda_caller
{
using pointer = std::decay_t< lambda >;
void
operator () (lambda & l) const noexcept
{
std::forward< lambda >(l)();
}
};
return std::unique_ptr< std::decay_t< lambda >, lambda_caller >(std::forward< lambda >(_lambda));
}
}
int
main()
{
std::cout << 1 << std::endl;
{
std::cout << 2 << std::endl;
[[gnu::unused]] auto && guard_ = make_scope_guard([&] { std::cout << __PRETTY_FUNCTION__ << std::endl; });
std::cout << 3 << std::endl;
}
std::cout << 5 << std::endl;
return EXIT_SUCCESS;
}
Such an approach works fine for simple pointer to free function void f() { std::cout << 4 << std::endl; }
passed to make_scope_guard
, but not for any lambda passed to make_scope_guard
.
This is due to an abundance of ... = pointer()
into the std::unique_ptr
definition (function default parameter, defaulting data memebers etc), but I can't find the DefaultConstructible requirement for pointer
into this article.
Is it mandatory, that the pointer
should match the std::is_default_constructible
requirement?
It tested against libc++
and against libstdc++
using not too old clang++ -std=gnu++1z
.
Seems, there should be language extension for lambdas: if auto l = [/* possible capture list */] (Args...) { /* code */; };
then using L = decltype(l);
is equivalent to struct L { constexpr void operator () (Args...) const noexcept { ; } };
for some Args...
, isn't it?
ADDITIONAL:
Providing the instance D{}
of following DefaultConstructible class to make_scope_guard(D{})
requires commented out code to be uncommented in the context if (p) { ...
, where p
is of type D
:
struct D { void operator () () const noexcept { std::cout << __PRETTY_FUNCTION__ << std::endl; } /* constexpr operator bool () const { return true; } */ };
unique_ptr is a new facility with a similar functionality, but with improved security. auto_ptr is a smart pointer that manages an object obtained via new expression and deletes that object when auto_ptr itself is destroyed.
In short: Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.
You can implement weak_ptr which works correctly with unique_ptr but only on the same thread - lock method will be unnecessary in this case.
A unique_ptr
is still a pointer. You cannot shoehorn a lambda into it. From [unique.ptr]:
A unique pointer is an object that owns another object and manages that other object through a pointer. More precisely, a unique pointer is an object u that stores a pointer to a second object p and will dispose of p when u is itself destroyed
[...]
Additionally, u can, upon request, transfer ownership to another unique pointer u2. Upon completion of such a transfer, the following post-conditions hold: [...] u.p is equal to
nullptr
A lambda is not a pointer. A lambda cannot equal nullptr
.
That said, you're already making your own local struct, why not just use that to do the RAII scope guarding itself instead of deferring to unique_ptr
? That seems like a hack at best, and takes more code to boot. You could instead just do:
template< typename lambda >
auto
make_scope_guard(lambda && _lambda)
{
struct lambda_caller
{
lambda _lambda;
~lambda_caller()
{
_lambda();
}
};
return lambda_caller{std::forward<lambda>(_lambda)};
}
If you need to support release
, you can wrap _lambda
inside of boost::optional
so that lambda_caller
becomes:
struct lambda_caller
{
boost::optional<lambda> _lambda;
~lambda_caller()
{
if (_lambda) {
(*_lambda)();
_lambda = boost::none;
}
}
void release() {
_lambda = boost::none;
}
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With