Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
333 views
in Technique[技术] by (71.8m points)

c++ - One-liner for RAII on non pointer?

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));
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...