Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

One-liner for RAII on non pointer?

Tags:

c++

winapi

raii

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));
like image 773
fjoanis Avatar asked Jul 07 '14 13:07

fjoanis


People also ask

What does RAII stand for?

Resource acquisition is initialization (RAII) is a programming idiom used in several object-oriented, statically-typed programming languages to describe a particular language behavior.

How does RAII work?

The principle that objects own resources is also known as "resource acquisition is initialization," or RAII. When a resource-owning stack object goes out of scope, its destructor is automatically invoked. In this way, garbage collection in C++ is closely related to object lifetime, and is deterministic.

What is the use of RAII in C++ programming?

Resource Acquisition Is Initialization or RAII, is a C++ programming technique which binds the life cycle of a resource that must be acquired before use (allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply) to the ...


2 Answers

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.

like image 120
rodrigo Avatar answered Oct 21 '22 10:10

rodrigo


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));
like image 41
fjoanis Avatar answered Oct 21 '22 09:10

fjoanis