Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unique_ptr and default constructible pointer

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; } */ };
like image 608
Tomilov Anatoliy Avatar asked Sep 27 '22 03:09

Tomilov Anatoliy


People also ask

What is the difference between Auto_ptr and unique_ptr?

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.

Should I use shared_ptr or unique_ptr?

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.

Can a Weak_ptr point to a unique_ptr?

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.


1 Answers

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;
    }
};
like image 93
Barry Avatar answered Oct 17 '22 13:10

Barry