One-liner for RAII on non pointer?
Asked Answered
S

6

17

Related topic

std::unique_ptr, deleters and the Win32 API

To use a Win32 Handle as a RAII, I can use the following line

std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&CloseHandle)> m_mutex(CreateMutex(NULL, FALSE, NULL), &::CloseHandle);

For me this is a clean one-liner and does exactly what I want.

When it comes to SOCKET, it won't compile with this same line since SOCKET cannot be nullptr.

What I need to do to make it work is the following :

struct SocketDeleter
{
    typedef SOCKET pointer;

    void operator()(SOCKET h) 
    { 
        ::closesocket(h);
    }
};

// Start listen socket.
std::unique_ptr<SOCKET, SocketDeleter> sock(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP));

What I don't like in this implementation is that any different type of ressources I'll want to use, I'll need to copy/paste the same code to only change the closing function.

I could use a Macro, but this is really ugly and can't be used twice

#define RAII_UNIQUE_RESOURCE(varName, classType, init, closure)  \
struct deleterMacro                                             \
{                                                               \
    typedef classType pointer;                                  \
    void operator()(classType h)                                \
    {                                                           \
        closure(h);                                             \
    }                                                           \
};                                                              \
std::unique_ptr<classType, deleterMacro> varName(init);

// Compile, but breaks as soon as 2 sockets defined.
RAII_UNIQUE_RESOURCE(sock, SOCKET, socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP), ::closesocket);

I tried to use a template, but I cannot pass my function pointer to the operator() function, as far as I know.

template<class T, class methodDeclaration, class pFuncPointer>
struct deleter
{
    typedef T pointer;

    void operator()(T h)
    {
        // Is there a way?? 
        methodDeclaration toCall = pFuncPointer;
        toCall(h);
    }
};
// With a call such as ...
std::unique_ptr<SOCKET, deleter<SOCKET, std::function<decltype(::closesocket)>, ::closesocket>> sock2(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP));
Shawndashawnee answered 7/7, 2014 at 13:1 Comment(11)
You'll probably want something like: template <typename T, typename D, D Deleter> struct stateless_deleter { using pointer = T; void operator()(T x) { Deleter(x); } }; Usage: std::unique_ptr<Socket, stateless_deleter<Socket, void(*)(Socket), &CloseSocket> p(OpenSocket());Smack
Why bother with std::unique_ptr? It will take 5 minutes to write a RAII wrapper or 30 sec to Google it up.Kan
@Kan why write a custom functionality if there is a standard one?Maleficent
@Maleficent If there is one. As I see there is no such.Kan
Ideas like this have been proposed before.Smack
'this is a clean one-liner and does exactly what I want' ... And for me it is not as clear as a proper class for resource managment. 'When it comes to SOCKET, it won't compile with this same line since SOCKET cannot be nullptr' and thats one reason to write proper abstraction, it is a MUTEX, a SOCKET, a FILE, a ... but not a Pointer. unique_ptr is not a general raii class for every resource.Bursarial
@fjoanis, since Kerrek SB answered in the comments instead of as an answer, feel free to make a self-answer (crediting Kerrek, of course) and remove the answer from the question.Melissa
@knivil: Actually, it doesn't have to be nullptr, it just has to be comparable-to nullptr (I think it is called NullablePointer). I have written a class that implements that for non-pointer-like types, if anybody is interested...Counterweight
@Counterweight I'm a taker for any answer which permits me to write only one line of code for any type of ressources. I just don't want to have to declare 1 structure for each kind of resource.Shawndashawnee
@fjoanis: You could wrap the small struct in a template and specify the function as a pointer-to-function template argument. But there are problems there with the function type and IMO, it is simply not worth it: the struct is just 4 lines. I've posted my code as an answer.Counterweight
It is like using std::tuple for every compound data. It is just wrong and would not pass code review in our company.Bursarial
S
4

Finally, I want with another Kerrek SB answer. It's the proposal for STD Unique Resource.

#ifndef UNIQUE_RESOURCE_H_
#define UNIQUE_RESOURCE_H_

#include <type_traits>

// From standard proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3949.pdf
// Slightly modified to compile on VS2012.
namespace std
{
    namespace experimental
    {
        enum class invoke_it { once, again };
        template<typename R,typename D>
        class unique_resource_t 
        {
            R resource;
            D deleter;
            bool execute_on_destruction; // exposition only
            unique_resource_t& operator=(unique_resource_t const &);
            unique_resource_t(unique_resource_t const &); // no copies!
        public:
            // construction
            explicit unique_resource_t(R && resource, D && deleter, bool shouldrun=true)
                : resource(std::move(resource))
                , deleter(std::move(deleter))
                , execute_on_destruction(shouldrun)
            {

            }
            // move
            unique_resource_t(unique_resource_t &&other) /*noexcept*/
                :resource(std::move(other.resource))
                ,deleter(std::move(other.deleter))
                ,execute_on_destruction(other.execute_on_destruction)
            {
                    other.release();
            }
            unique_resource_t& operator=(unique_resource_t &&other) 
            {
                this->invoke(invoke_it::once);
                deleter=std::move(other.deleter);
                resource=std::move(other.resource);
                execute_on_destruction=other.execute_on_destruction;
                other.release();
                return *this;
            }
            // resource release
            ~unique_resource_t() 
            {
                this->invoke(invoke_it::once);
            }
            void invoke(invoke_it const strategy = invoke_it::once) 
            {
                if (execute_on_destruction) {
                    try {
                        this->get_deleter()(resource);
                    } catch(...){}
                }
                execute_on_destruction = strategy==invoke_it::again;
            }
            R const & release() /*noexcept*/{
                execute_on_destruction = false;
                return this->get();
            }
            void reset(R && newresource) /*noexcept*/ {
                invoke(invoke_it::again);
                resource = std::move(newresource);
            }
            // resource access
            R const & get() const /*noexcept*/ {
                return resource;
            }
            operator R const &() const /*noexcept*/ 
            {
                return resource;
            }
            R operator->() const /*noexcept*/ 
            {
                return resource;
            }

//             Couldn't make this function compile on VS2012
//             std::add_lvalue_reference<std::remove_pointer<R>::type>::type operator*() const 
//             {
//                     return * resource;
//             }

            // deleter access
            const D & get_deleter() const /*noexcept*/ 
            {
                return deleter;
            }
        };

        //factories
        template<typename R,typename D>
        unique_resource_t<R,D> unique_resource( R && r,D t) /*noexcept*/ 
        {
                return unique_resource_t<R,D>(std::move(r), std::move(t),true);
        }
            template<typename R,typename D>
        unique_resource_t<R,D>
            unique_resource_checked(R r, R invalid, D t ) /*noexcept*/ {
                bool shouldrun = (r != invalid);
                return unique_resource_t<R,D>(std::move(r), std::move(t), shouldrun);
        }
    }
}
#endif /* UNIQUE RESOURCE H */

Usage

auto listenSocket = std::experimental::unique_resource_checked(socket(AF_INET,SOCK_STREAM,IPPROTO_TCP), INVALID_SOCKET, closesocket);

Hope this makes std soon enough!

Shawndashawnee answered 15/7, 2014 at 13:29 Comment(1)
It did not make it into C++17 unfortunately, but the proposal is still activeTetrafluoroethylene
C
13

It is well known the example to RAII a FILE* using std::unique_ptr:

struct FILEDeleter
{
    typedef FILE *pointer;
    void operator()(FILE *fp) { fclose(fp); }
};

typedef std::unique_ptr<FILE, FILEDeleter> FilePtr;

FilePtr f(fopen("file.txt", "r"));

Alas, a similar approach to POSIX close() to RAII a file descriptor is not possible:

struct FDDeleter
{
    typedef int pointer;
    void operator()(int fd) { close(fp); }
};

typedef std::unique_ptr<int, FDDeleter> FD;

Although some compilers will work just fine, it is not valid because the fd==0 is a valid file descriptor! The null one should be -1. But anyway, even if it were 0 it is still not valid, because FDDeleter::pointer shall satisfy the requirements of NullablePointer (summing up):

  1. It shall be comparable to nullptr.
  2. It shall be value-initialized to a value that compares equal to nullptr.

Thus, UniqueHandle is born!

#include <memory>

template <typename T, T TNul = T()>
class UniqueHandle
{
public:
    UniqueHandle(std::nullptr_t = nullptr)
        :m_id(TNul)
    { }
    UniqueHandle(T x)
        :m_id(x)
    { }
    explicit operator bool() const { return m_id != TNul; }

    operator T&() { return m_id; }
    operator T() const { return m_id; }

    T *operator&() { return &m_id; }
    const T *operator&() const { return &m_id; }

    friend bool operator == (UniqueHandle a, UniqueHandle b) { return a.m_id == b.m_id; }
    friend bool operator != (UniqueHandle a, UniqueHandle b) { return a.m_id != b.m_id; }
    friend bool operator == (UniqueHandle a, std::nullptr_t) { return a.m_id == TNul; }
    friend bool operator != (UniqueHandle a, std::nullptr_t) { return a.m_id != TNul; }
    friend bool operator == (std::nullptr_t, UniqueHandle b) { return TNul == b.m_id; }
    friend bool operator != (std::nullptr_t, UniqueHandle b) { return TNul != b.m_id; }

private:
    T m_id;
};

Its use is pretty easy, best seen with an example:

struct FDDeleter
{
    typedef UniqueHandle<int, -1> pointer;
    void operator()(pointer p)
    {
        close(p);
    }
};
typedef std::unique_ptr<int, FDDeleter> FD;

FD fd(open("test.txt", O_RDONLY));

If you truly want a one-liner you could go with this generalization:

template <typename T, T TNul = T(), typename RD, RD (*D)(T)>
struct OLDeleter
{
    typedef UniqueHandle<T, TNul> pointer;
    void operator()(pointer p)
    {
        D(p);
    }
};

And then just one line:

std::unique_ptr<int, OLDeleter<int, -1, int, close> > FD fd(open("test.txt", O_RDONLY));

The problem is that you must add the return of close() as a template argument and assume that there isn't anything funny about this function that prevents its conversion to a int(*)(int) (weird calling conventions, extra parameters, macros...) and that is quite inconvenient.

You could add a function wrapper:

void my_close(int fd) { close(fd); }

But if you are into it, you could as well write the whole struct FDDeleter.

Counterweight answered 7/7, 2014 at 16:24 Comment(0)
S
5

Kerrek SB answered in the comments and it was exactly what I was looking for!

template <typename T, typename D, D Deleter> 
struct stateless_deleter 
{
    typedef T pointer; 

    void operator()(T x) 
    { 
        Deleter(x); 
    } 
};
std::unique_ptr<SOCKET, stateless_deleter<SOCKET, int(*)(SOCKET), &::closesocket>> listenSocket(socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_UDP));
Shawndashawnee answered 7/7, 2014 at 15:7 Comment(3)
Ohh, I didn't realize unique_ptr checked for a pointer type policy from its deleter. Neat.Stability
But this is only valid if T is is a pointer type. If not, it will not satisfy the unique_ptr requirements of NullablePointer. And IIRC, Windows SOCKET is a typedef of int or long, and those do not qualify.Counterweight
This code is ill-formed ; NullablePointer requires amongst other things that x != nullptr be valid (where x has the type of D::pointer)Tetrafluoroethylene
S
4

Finally, I want with another Kerrek SB answer. It's the proposal for STD Unique Resource.

#ifndef UNIQUE_RESOURCE_H_
#define UNIQUE_RESOURCE_H_

#include <type_traits>

// From standard proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3949.pdf
// Slightly modified to compile on VS2012.
namespace std
{
    namespace experimental
    {
        enum class invoke_it { once, again };
        template<typename R,typename D>
        class unique_resource_t 
        {
            R resource;
            D deleter;
            bool execute_on_destruction; // exposition only
            unique_resource_t& operator=(unique_resource_t const &);
            unique_resource_t(unique_resource_t const &); // no copies!
        public:
            // construction
            explicit unique_resource_t(R && resource, D && deleter, bool shouldrun=true)
                : resource(std::move(resource))
                , deleter(std::move(deleter))
                , execute_on_destruction(shouldrun)
            {

            }
            // move
            unique_resource_t(unique_resource_t &&other) /*noexcept*/
                :resource(std::move(other.resource))
                ,deleter(std::move(other.deleter))
                ,execute_on_destruction(other.execute_on_destruction)
            {
                    other.release();
            }
            unique_resource_t& operator=(unique_resource_t &&other) 
            {
                this->invoke(invoke_it::once);
                deleter=std::move(other.deleter);
                resource=std::move(other.resource);
                execute_on_destruction=other.execute_on_destruction;
                other.release();
                return *this;
            }
            // resource release
            ~unique_resource_t() 
            {
                this->invoke(invoke_it::once);
            }
            void invoke(invoke_it const strategy = invoke_it::once) 
            {
                if (execute_on_destruction) {
                    try {
                        this->get_deleter()(resource);
                    } catch(...){}
                }
                execute_on_destruction = strategy==invoke_it::again;
            }
            R const & release() /*noexcept*/{
                execute_on_destruction = false;
                return this->get();
            }
            void reset(R && newresource) /*noexcept*/ {
                invoke(invoke_it::again);
                resource = std::move(newresource);
            }
            // resource access
            R const & get() const /*noexcept*/ {
                return resource;
            }
            operator R const &() const /*noexcept*/ 
            {
                return resource;
            }
            R operator->() const /*noexcept*/ 
            {
                return resource;
            }

//             Couldn't make this function compile on VS2012
//             std::add_lvalue_reference<std::remove_pointer<R>::type>::type operator*() const 
//             {
//                     return * resource;
//             }

            // deleter access
            const D & get_deleter() const /*noexcept*/ 
            {
                return deleter;
            }
        };

        //factories
        template<typename R,typename D>
        unique_resource_t<R,D> unique_resource( R && r,D t) /*noexcept*/ 
        {
                return unique_resource_t<R,D>(std::move(r), std::move(t),true);
        }
            template<typename R,typename D>
        unique_resource_t<R,D>
            unique_resource_checked(R r, R invalid, D t ) /*noexcept*/ {
                bool shouldrun = (r != invalid);
                return unique_resource_t<R,D>(std::move(r), std::move(t), shouldrun);
        }
    }
}
#endif /* UNIQUE RESOURCE H */

Usage

auto listenSocket = std::experimental::unique_resource_checked(socket(AF_INET,SOCK_STREAM,IPPROTO_TCP), INVALID_SOCKET, closesocket);

Hope this makes std soon enough!

Shawndashawnee answered 15/7, 2014 at 13:29 Comment(1)
It did not make it into C++17 unfortunately, but the proposal is still activeTetrafluoroethylene
T
3

I often use this in C++11:

 #include <utility>

 namespace{
    template<typename F>
    struct RAII_Helper{
        template<typename InitFunction>
        RAII_Helper(InitFunction &&init, F &&exit) : f_(std::forward<F>(exit)), canceled(false){
            init();
        }
        RAII_Helper(F &&f) : f_(f), canceled(false){
        }
        ~RAII_Helper(){
            if (!canceled)
                f_();
        }
        void cancel(){
            canceled = true;
        }
    private:
        F f_;
        bool canceled;
    };
 }
 template<class F>
 RAII_Helper<F> RAII_do(F &&f){
    return RAII_Helper<F>(std::forward<F>(f));
 }

 template<class Init, class Exit>
 RAII_Helper<Exit> RAII_do(Init &&init, Exit &&exit){
    return RAII_Helper<Exit>(std::forward<Init>(init), std::forward<Exit>(exit));
 }

usage:

FILE *f = fopen("file", "r");
if (!f)
    return "error";
auto filecloser = RAII_do([=]{fclose(f);});

//also makes for good init / exit objects
static auto initExit = RAII_do([]{initializeLibrary();}, []{exitLibrary();});

I like it because it works for arbitrary code, not just pointers or handles. Also the cancel function could be omitted if never used.

Temporize answered 15/7, 2014 at 14:2 Comment(1)
Your class misses move operators or you'll be able to copy it leading to double free/close/release!Woodham
E
2

Here's one possible solution using as an example the NetCDF C API, which has plain ints as handles:

retval = nc_open(..., &id);
...  // validate
std::unique_ptr<int, void(*)(int*)> always_be_closing(&id, [](int* p){nc_close(*p);});

Of course you can check the value in the lambda, if necessary.

... [](int* p){ if(p) nc_close(*p); }

And a typedef makes it a little nicer:

typedef std::unique_ptr<int, void(*)(int*)> nc_closer;
...
nc_closer abc(&id, [](int* p){nc_close(*p);});

And you probably want a function to reduce duplication:

static void nc_close_p(int* p) { nc_close(*p); }
...
nc_closer abc(&id, &nc_close_p);

or:

auto abc = auto_nc_close(&id, &nc_close_p);

Since unique_ptr implements operator bool, you can also use this as a block scope, like using in C#:

if (auto abc = auto_nc_close(&id, &nc_close_p))
{
    ...
}
Eger answered 27/9, 2017 at 18:50 Comment(0)
H
1

A slightly different approach (within the premises of the RAII idiom though) is to use boost's scope exit.

Example :

#include <boost/scope_exit.hpp>
#include <cstdlib>
#include <cstdio>
#include <cassert>

int main() 
{
    std::FILE* f = std::fopen("example_file.txt", "w");
    assert(f);

    BOOST_SCOPE_EXIT(f) {
    // Whatever happened in scope, this code will be
    // executed  and  file  will be correctly closed.
        std::fclose(f);
    } BOOST_SCOPE_EXIT_END

    // Some code that may throw or return.
    // ...
}

Using this functionality, you'd be practically specifying freestanding "RAII destructor actions". Use where it makes your code clearer and cleaner and avoid when all functionality would be more easily incorporated (or already is) inside a class' destructor.


It seems that soon additional RAII functionality will be added to the language. When available you'll be able to use something like scoped_resource which looks like this (I'd refer to that link for a full fledged implementation of what you ask)

Haynor answered 7/7, 2014 at 18:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.