3

I stumbled onto this problem: Using unique_ptr to control a file descriptor. The std::unique_ptr is not really appropriate for a general handle. So is the more general class

template<class HandleType,HandleType nullvalue,class Deleter>
class Handle;

already implemented (maybe in boost), or should I roll my own. This question has been raised before in What wrapper class in C++ should I use for automated resource management?, but now we have C++14 so there can be more alternatives.

I also found the following proposal: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3677.html. So someone else has also thought about this problem.

Community
  • 1
  • 1
user877329
  • 6,717
  • 8
  • 46
  • 88
  • 2
    A separate "deleter" class or function is unnecessarily over-complicated. Just wrap the file descriptor in a class, whose destructor closes the file descriptor. Then, use whichever smart pointer you want: unique_ptr, or shared_ptr. – Sam Varshavchik Jun 19 '16 at 12:54
  • @SamVarshavchik : .. or don't use ptrs at all, just move constructors. – lorro Jun 19 '16 at 13:07
  • 1
    Honestly, I still think jalf's answer is the best one, even in the world of C++14. Each implementation will need to call different functions in the constructor and destructor, and the code is just not complicated enough to make a reusable class worth the effort. – Cody Gray - on strike Jun 19 '16 at 13:41
  • @CodeGray. Why was the `unique_ptr` written then. After all, that is a special case: `template class unique_ptr:Handle` + the `operator*` which does not make sense in the general case. – user877329 Jun 19 '16 at 14:04
  • Uhh, unique_ptr is designed to manage the lifetime of pointers. In most cases, you do not need to specialize it. You just let the default implementation call delete. To handle other [very rare] cases, you can provide your own specialization with a custom deleter. I don't really know what that has to do with a wrapper class for specific resources. The generic class would literally have *no code* in it, you'd have to provide the entire implementation yourself. What would be the point? – Cody Gray - on strike Jun 19 '16 at 16:05
  • @CodyGray Other resources behave the same way. The only difference is their type and deleter, and the latter is handled correctly in `std::unique_ptr`. The generic class the contains appropriate Ctors (init, and move [copy=delete]), the move assignment operator, a getter and the destructor (calls deleter if resource not empty). Notice that `unique_ptr` does _not_ create the resource on its own. – user877329 Jun 19 '16 at 18:53

1 Answers1

3

There's no such class in C++ standard yet, so I decided to write my own:

template<typename Policy>
class unique_handle
{
    typename Policy::handle_type h;

public:
    unique_handle(const unique_handle&) = delete;

    typename Policy::handle_type get() const
    {
        return h;
    }

    typename Policy::handle_type release()
    {
        typename Policy::handle_type temp = h;
        h = Policy::get_null();
        return temp;
    }

    explicit operator bool() const
    {
        return !Policy::is_null(h);
    }

    bool operator!() const
    {
        return !static_cast<bool>(*this);
    }

    void reset(typename Policy::handle_type new_handle)
    {
        typename Policy::handle_type old_handle = h;
        h = new_handle;
        if(!Policy::is_null(old_handle))
        {
            Policy::close(old_handle);
        }
    }

    void swap(unique_handle& other)
    {
        std::swap(this->h, other.h);
    }

    void reset()
    {
        reset(Policy::get_null());
    }

    ~unique_handle()
    {
        reset();
    }

    unique_handle& operator=(unique_handle other) noexcept
    {
        this->swap(other);
        return *this;
    }

    unique_handle(unique_handle&& other) noexcept
    {
        this->h = other.h;
        other.h = Policy::get_null();
    }

    unique_handle()
    {
        h = Policy::get_null();
    }

    unique_handle(typename Policy::handle_type handle)
    {
        h = handle;
    }
};

sample usage:

#include <wiertlo/unique_handle.hpp>
#include <iostream>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

struct file_descriptor_policy
{
    typedef int handle_type;
    static void close(handle_type handle)
    {
        ::close(handle);
    }

    static handle_type get_null()
    {
        return -1;
    }

    static bool is_null(handle_type handle)
    {
        return handle == -1;
    }
};

int main()
{
    typedef wiertlo::unique_handle<file_descriptor_policy> file_descriptor;
    file_descriptor null_fd; // null file descriptor
    assert(!null_fd);
    file_descriptor fd(open("/dev/null", O_WRONLY));
    assert(fd);
    write(fd.get(), "test", 4);
    file_descriptor other = std::move(fd);
    assert(!fd);
    assert(other);
}
milleniumbug
  • 15,379
  • 3
  • 47
  • 71