Однострочный для RAII по не указателю?

Связанная тема

std:: unique_ptr, удалители и API Win32

Чтобы использовать ручку 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));

Ответ 1

Наконец, я хочу с другим ответом 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);

Надеюсь, что это сделает std достаточно скоро!

Ответ 2

Хорошо известен пример для RAII a 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 (суммирование):

  • Он должен быть сопоставим с nullptr.
  • Инициализируется значением для значения, равного 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.

Ответ 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));

Ответ 4

Я часто использую это в С++ 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

Немного другой подход (в пределах помещения идиомы 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.
    // ...
}

Используя эту функциональность, вы практически определяете автономные действия RAIM-деструктора. Используйте, где он делает ваш код более четким и чистым, и избегайте, когда вся функциональность будет легче включаться (или уже есть) внутри деструктора класса.


Похоже, что скоро добавлена ​​дополнительная функциональность RAII. Когда доступно, вы сможете использовать что-то вроде scoped_resource, которое выглядит как this (я бы назвал эта ссылка для полноценной реализации того, что вы просите)

Ответ 6

Здесь одно возможное решение, использующее в качестве примера API NetCDF C, который имеет простые ints как дескрипторы:

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

Конечно, вы можете проверить значение в лямбда, если это необходимо.

... [](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, вы также можете использовать его как область блока, например using в С#:

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