非指针上的RAII单线程?

时间:2014-07-07 13:01:27

标签: c++ winapi raii

相关主题

std::unique_ptr, deleters and the Win32 API

要将Win32句柄用作RAII,我可以使用以下行

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

对我而言,这是一个干净的单行,并且完全符合我的要求。

说到SOCKET,它不会用同一行编译,因为SOCKET不能是nullptr。

要使其发挥作用,我需要做的是:

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

我在这个实现中不喜欢的是我想要使用的任何不同类型的资源,我需要复制/粘贴相同的代码才能更改关闭函数。

我可以使用宏,但这真的很丑,不能使用两次

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

我试图使用模板,但据我所知,我无法将函数指针传递给operator()函数。

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

6 个答案:

答案 0 :(得分:10)

众所周知,RA FILE*使用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"));

唉,POSIX close()类似于RAII文件描述符的方法是不可能的:

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

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

虽然有些编译器可以正常工作,但它无效,因为fd==0是一个有效的文件描述符!空值应为-1。但无论如何,即使它是0它仍然无效,因为FDDeleter::pointer应满足 NullablePointer 的要求(总结):

  1. 应与nullptr相当。
  2. 应将其值初始化为比较等于nullptr的值。
  3. 因此,UniqueHandle诞生了!

    #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;
    };
    

    它的使用非常简单,最好用一个例子来看:

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

    如果你真的想要一个单行,你可以采用这种概括:

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

    然后只有一行:

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

    问题是你必须添加close()的返回值作为模板参数,并假设这个函数没有任何有趣的东西阻止它转换为int(*)(int)(奇怪的调用约定,额外的参数,宏...),这是非常不方便的。

    您可以添加函数包装器:

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

    但如果你参与其中,你也可以写出整个struct FDDeleter

答案 1 :(得分:3)

Kerrek SB在评论中回答说,这正是我想要的!

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

答案 2 :(得分:3)

我经常在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));
 }

用法:

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

我喜欢它,因为它适用于任意代码,而不仅仅是指针或句柄。如果从未使用过,也可以省略取消功能。

答案 3 :(得分:2)

最后,我想要另一个Kerrek SB答案。这是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 */

用法

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

希望这很快就会成为标准!

答案 4 :(得分:1)

稍微不同的方法(在RAII习语的前提下)是使用提升范围退出

示例:

#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.
    // ...
}

使用this功能,您实际上可以指定独立的&#34; RAII析构函数&#34;。使用它可以使您的代码更清晰,更清晰,并避免在所有功能更容易合并(或已经是)在课程内部时使用。析构函数。


该语言似乎会添加soon 其他RAII功能。如果可用,您将能够使用类似thisscoped_resource之类的内容(我将引用该链接以完全了解您的要求)

答案 5 :(得分:1)

这是一个可能的解决方案,以NetCDF C API为例,它具有简单的整体作为句柄:

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

当然,如有必要,您可以检查lambda中的值。

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

并且typedef使它更好一些:

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

你可能想要一个减少重复的功能:

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

或:

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

由于unique_ptr实现了operator bool,您也可以将其用作块范围,例如C#中的using

if (auto abc = auto_nc_close(&id, &nc_close_p))
{
    ...
}