Is there a proper 'ownership-in-a-package' for 'handles' available?

不问归期 提交于 2019-11-27 16:14:26

The type unique_ptr is less general than the phrase "handle", yes. But why shouldn't it be? Just one of your "handle" examples (say, the one that is an integer index), is precisely as general as unique_ptr. You can't compare one specific kind of handle with "all handles ever".

If you want a single, concrete C++ type (or type template) that is a handle without actually defining any specific handling semantics then... I can't help you. I don't think anyone tractibly could.

chico

This is a toy example of "type disguising" I was having fun, that also has something to do with the discussion. I'll leave it here for reference (I'm not responsible for any harm caused by it!).

#ifndef _DISGUISE_HPP_
#define _DISGUISE_HPP_

#include <cassert>
#include <utility>
#include <iostream>

namespace disguise
{
    template<typename T>
    struct default_release { void operator()(const T &) const { std::cout << "I'M DUMB" << std::endl; } };

    template<typename T>
    struct default_release<T *> { void operator()(T *p) const { std::cout << "I'M SMART" << std::endl; delete p; } };

    template<typename R>
    struct default_release<R ()> { void operator()(R (*f)()) const { std::cout << "I'M A SCOPE-EXIT FUNCTION" << std::endl; f(); } };

    template<typename R>
    struct default_release<R (*)()> { void operator()(R (*f)()) const { std::cout << "I'M A SCOPE-EXIT FUNCTION POINTER" << std::endl; f(); } };

    template<typename F, F> struct releaser;

    //template<typename R, typename P, R F(P)> struct releaser_impl_f { void operator()(P v) const { F(v); } };
    template<typename R, typename P, R (*F)(P)> struct releaser_impl_p { void operator()(P v) const { F(v); } };

    //template<typename R, typename P, R F(P)> struct releaser<R (P), F> : releaser_impl_f<R, P, F> {};
    template<typename R, typename P, R (*F)(P)> struct releaser<R (*)(P), F> : releaser_impl_p<R, P, F> {};

    #define RELEASER(f) disguise::releaser<decltype(f), (f)>

    template<typename T, typename F>
    class unique_impl
    {
        T v;
        F f;
        bool empty = true;

    public:

        unique_impl() {}
        unique_impl(const unique_impl &) = delete;
        unique_impl &operator=(const unique_impl &) = delete;

        unique_impl &operator=(unique_impl &&other)
        {
            assert(!other.empty);
            if(!empty) f(v);
            v = std::move(other.v);
            f = std::move(other.f);
            empty = false;
            other.empty = true;
            return *this;
        }

        unique_impl(unique_impl &&other) { assert(!other.empty); *this = std::move(other); }

        unique_impl(const T &v_, const F &f_ = F()) : v(v_), f(f_), empty(false) {}
        unique_impl(T &&v_, const F &f_ = F()) : v(std::move(v_)), f(f_), empty(false) {}
        unique_impl &operator=(const T &v_) { if(!empty) f(v); v = v_; empty = false; return *this; }
        unique_impl &operator=(T &&v_) { if(!empty) f(v); v = std::move(v_); empty = false; return *this; }

        ~unique_impl() { if(!empty) f(v); }

        operator T () const { assert(!empty); return v; }
        unique_impl &operator+=(const T& v_) { assert(!empty); v += v_; return *this; }
        unique_impl &operator-=(const T& v_) { assert(!empty); v -= v_; return *this; }
        unique_impl &operator*=(const T& v_) { assert(!empty); v *= v_; return *this; }
        unique_impl &operator/=(const T& v_) { assert(!empty); v /= v_; return *this; }
        unique_impl &operator%=(const T& v_) { assert(!empty); v %= v_; return *this; }
        unique_impl &operator|=(const T& v_) { assert(!empty); v |= v_; return *this; }
        unique_impl &operator&=(const T& v_) { assert(!empty); v &= v_; return *this; }
        unique_impl &operator^=(const T& v_) { assert(!empty); v ^= v_; return *this; }
        unique_impl &operator>>=(const T& v_) { assert(!empty); v >>= v_; return *this; }
        unique_impl &operator<<=(const T& v_) { assert(!empty); v <<= v_; return *this; }
        unique_impl &operator++() { assert(!empty); ++v; return *this; }
        unique_impl &operator--() { assert(!empty); --v; return *this; }
        T operator++(int) { assert(!empty); return v++; }
        T operator--(int) { assert(!empty); return v--; }
        const T &operator->() const { assert(!empty); return v; }
        T &operator->() { assert(!empty); return v; }
        const T *operator&() const { assert(!empty); return &v; }
        T *operator&() { assert(!empty); return &v; }
    };

    template<typename T, typename F = default_release<T>>
    struct unique : unique_impl<T, F>
    {
        unique() {}
        unique(unique &&) = default;
        unique &operator=(unique &&) = default;
        unique(const unique &) = delete;
        unique &operator=(const unique &) = delete;

        unique(const T &v_, const F &f_ = F()) : unique_impl<T, F>(v_, f_) {}
        unique(T &&v_, const F &f_ = F()) : unique_impl<T, F>(std::move(v_), f_) {}
    };

    template<typename R, typename F>
    struct unique<R (), F> : unique_impl<R (*)(), F>
    {
        typedef R (*T)();

        unique() {}
        unique(unique &&) = default;
        unique &operator=(unique &&) = default;
        unique(const unique &) = delete;
        unique &operator=(const unique &) = delete;

        unique(const T &v_, const F &f_ = F()) : unique_impl<T, F>(v_, f_) {}
        unique(T &&v_, const F &f_ = F()) : unique_impl<T, F>(std::move(v_), f_) {}
    };
}

#endif // _DISGUISE_HPP_

Test:

#include <disguise.hpp>

using namespace disguise;

void disguised_address(const int *i)
{
    std::cout << *i << std::endl;
}

typedef void *handle;
handle create_handle(){ std::cout << "creating a handle" << std::endl; return static_cast<handle>(new int); };
void release_handle(handle h){ std::cout << "releasing a handle" << std::endl; delete static_cast<int *>(h); };
void manipulate_handle(handle h) { std::cout << "manipulating handle" << std::endl; }

int main()
{
    unique<void()> f = { []{ std::cout << "Hi" << std::endl; } };
    f = { []{ std::cout << "Bye" << std::endl; } };

    {
        int i;

        i = 10;
        std::cout << i << std::endl;
        i++;
        std::cout << i << std::endl;
        if(i > 0)
            std::cout << "i > 0" << std::endl;
        int j = i;
        j += i;
        std::cout << "j == " << j << std::endl;
        disguised_address(&i);
    }

    {
        unique<int> i;

        i = 10;
        std::cout << i << std::endl;
        i++;
        std::cout << i << std::endl;
        if(i > 0)
            std::cout << "i > 0" << std::endl;
        int j = i;
        j += i;
        std::cout << "j == " << j << std::endl;
        disguised_address(&i);
    }

    struct X{ int x = 10; void hello() const { std::cout << "hello" << std::endl; } };

    unique<X *> out;

    {
        X *x = new X;
        unique<X *> p = x;
        std::cout << p->x << " == " << x->x << std::endl;
        std::cout << (*p).x << " == " << (*x).x << std::endl;
        p->hello();
        (*p).hello();
        out = std::move(p);
    }

    std::cout << "Any smart destruction?" << std::endl;

    {
        unique<X *> p = std::move(out);
    }

    std::cout << "How about now?" << std::endl;

    {
        using unique_handle = unique<handle, RELEASER(&release_handle)>;

        unique_handle h = create_handle();
        manipulate_handle(h);
    }

    {
        unique<handle, decltype(&release_handle)> h = { create_handle(), release_handle };
        manipulate_handle(h);
    }
}

Output:

I'M A SCOPE-EXIT FUNCTION
Hi
10
11
i > 0
j == 22
11
10
11
i > 0
j == 22
11
I'M DUMB
10 == 10
10 == 10
hello
hello
Any smart destruction?
I'M SMART
How about now?
creating a handle
manipulating handle
releasing a handle
creating a handle
manipulating handle
releasing a handle
I'M A SCOPE-EXIT FUNCTION
Bye

I would have had more fun if .(dot) could be overloaded =)

chico

This is a rough sketch of the tool I ask, for the case of unique ownership. There's no need to peer inside the native/legacy handle type, no encapsulation violation, no ugly extra wrapping required. Works for almost all kinds of legacy handles, integral or pointer based.

It provides one extra feature, beyond resource management, which is impersonation of the raw handle, I find it somewhat useful.

#include <utility>
#include <cassert>

template<typename T>
struct default_release
{
    void operator ()(const T &) {  }
};

template<typename Handle, typename Release = default_release<Handle>>
class unique_handle
{
    Handle handle;
    Release release;
    bool empty = true;

public:
    unique_handle(const Handle &handle_, const Release &release_ = Release()):
        handle(handle_),
        release(release_),
        empty(false) {}

    unique_handle &operator=(const unique_handle &) = delete;

    unique_handle(const unique_handle &) = delete;

    unique_handle &operator=(unique_handle &&other)
    {
        assert(!other.empty);
        if(!empty)
            release(handle);
        handle = std::move(other.handle);
        release = std::move(other.release);
        empty = false;
        other.empty = true;
        return *this;
    }

    unique_handle(unique_handle &&other)
    {
        *this = std::move(other);
    }

    operator Handle () const
    {
        assert(!empty);
        return handle;
    }

    ~unique_handle()
    {
        if(!empty)
            release(handle);
    }
};

This is some sample usage:

// Types that model some C API
typedef unsigned GLuint;
typedef int GLsizei;

GLuint glGenLists(GLsizei range);
void glCallList(GLuint list);
void glDeleteLists(GLuint list, GLsizei range);

typedef void* HMODULE;
typedef const char *LPCSTR;
typedef int BOOL;
typedef int (*FARPROC) ();

HMODULE LoadLibraryA(LPCSTR lpFileName);
BOOL FreeLibraryA(HMODULE hModule);

FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);

// The client code
unique_handle<HMODULE, decltype(&FreeLibraryA)> grab_foobar_lib()
{
    return {LoadLibraryA("foo/bar"), &FreeLibraryA};
}

int main()
{
    // using a short lambda here for the case of a
    // non-trivial deleter with more than one argument 
    unique_handle<GLuint, void (*)(GLuint)> gl_list_handle(glGenLists(1), [](GLuint list){ if(list != 0) glDeleteLists(list, 1); });

    glCallList(gl_list_handle); // using "impersonation" of the raw handle in the C API

    typedef void (*func)();
    auto the_lib = grab_foobar_lib(); // potential move semantics
    if(the_lib != NULL)
        func f = (func) GetProcAddress(the_lib, "f"); // more impersonation
}

EDIT

It's surprising how people follow a similar scenario for such situations. This, for example, contains the same handle impersonation feature:

https://github.com/adobe/chromium/blob/master/base/win/scoped_handle.h

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!