【问题标题】:One-liner for RAII on non pointer?非指针上的 RAII 单线?
【发布时间】:2014-07-07 13:01:26
【问题描述】:

相关主题

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

【问题讨论】:

  • 你可能想要这样的东西:template &lt;typename T, typename D, D Deleter&gt; struct stateless_deleter { using pointer = T; void operator()(T x) { Deleter(x); } }; 用法:std::unique_ptr&lt;Socket, stateless_deleter&lt;Socket, void(*)(Socket), &amp;CloseSocket&gt; p(OpenSocket());
  • 为什么要打扰std::unique_ptr?编写一个 RAII 包装器需要 5 分钟,谷歌搜索需要 30 秒。
  • @Drop 为什么要编写自定义功能如果有一个标准功能?
  • @UmNyobe 如果有一个。正如我所见,没有这样的。
  • 这样的想法have beenproposedbefore

标签: c++ winapi raii


【解决方案1】:

众所周知,使用std::unique_ptr RAII a FILE* 的示例:

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的值。

于是,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

【讨论】:

    【解决方案2】:

    Kerrek SB 在 cmets 中给出了答案,这正是我想要的!

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

    【讨论】:

    • 哦,我没有意识到 unique_ptr 从其删除器中检查了 pointer 类型策略。整洁。
    • 但这仅在T 是指针类型时才有效。如果不是,它将不满足 NullablePointerunique_ptr 要求。而 IIRC,Windows SOCKETintlong 的 typedef,这些都不符合条件。
    • 这段代码格式不正确; NullablePointer 要求x != nullptr 有效(其中x 的类型为D::pointer
    【解决方案3】:

    最后,我想要另一个 Kerrek SB 答案。这是STD唯一资源的提案。

    #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】:

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

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

    【讨论】:

    • 您的课程缺少移动运算符,否则您将能够复制它导致双重释放/关闭/释放!
    【解决方案5】:

    这是一个可能的解决方案,以 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))
    {
        ...
    }
    

    【讨论】:

      【解决方案6】:

      一种稍微不同的方法(尽管在 RAII 惯用语的前提下)是使用 boost 的范围退出

      例子:

      #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 功能,您实际上是在指定独立的“RAII 析构函数”。使用它可以使您的代码更清晰、更干净,并避免所有功能更容易合并(或已经存在)在类的析构函数中。


      似乎soon 额外的 RAII 功能将被添加到语言中。如果可用,您将能够使用类似 scoped_resource 的东西,看起来像 this(我会参考该链接以获得您所要求的完整实现)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-28
        • 2011-01-12
        • 1970-01-01
        • 2012-04-12
        • 1970-01-01
        • 2020-11-18
        • 2023-04-07
        • 1970-01-01
        相关资源
        最近更新 更多