Search code examples
c++winapiraii

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));

Solution

  • 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!