Using unique_ptr to control a file descriptor

与世无争的帅哥 提交于 2019-12-17 19:23:02

问题


In theory, I should be able to use a custom pointer type and deleter in order to have unique_ptr manage an object that is not a pointer. I tried the following code:

#ifndef UNIQUE_FD_H
#define UNIQUE_FD_H

#include <memory>
#include <unistd.h>

struct unique_fd_deleter {
    typedef int pointer; // Internal type is a pointer

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

typedef std::unique_ptr<int, unique_fd_deleter> unique_fd;

#endif // UNIQUE_FD_H

This doesn't work (gcc 4.7 with the -std=c++11 parameter). It responds with the following errors:

In file included from /usr/include/c++/4.7/memory:86:0,
                 from test.cc:6:
/usr/include/c++/4.7/bits/unique_ptr.h: In instantiation of 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = int; _Dp = unique_fd_deleter]':
test.cc:22:55:   required from here
/usr/include/c++/4.7/bits/unique_ptr.h:172:2: error: invalid operands of types 'int' and 'std::nullptr_t' to binary 'operator!='

From delving into the definition of unique_ptr, I can see two problems that prevent it from working. The first, which seems in clear violation of the standard, is that the destructor for unique_ptr compares the "pointer" (which is, as per my definition, an int) to nullptr in order to see whether it is initialized or not. This is in contrast to the way it reports it through the boolean conversion, which is to compare it to "pointer()" (an uninitialized "pointer"). This is the cause of the errors I am seeing - an integer is not comparable to a nullptr.

The second problem is that I need some way to tell unique_ptr what an uninitialized value is. I want the following snippet to work:

unique_fd fd( open(something...) );

if( !fd )
    throw errno_exception("Open failed");

For that to work, unique_ptr needs to know that an "uninitialized value" is -1, as zero is a valid file descriptor.

Is this a bug in gcc, or am I trying to do something here that simply cannot be done?


回答1:


The type exposed by the Deleter::pointer must satisfy the NullablePointer requirements. Chief among them, this expression must be legal: Deleter::pointer p = nullptr;. Of course, nullptr is pretty much defined by the fact that it cannot be implicitly converted to a number, thus this doesn't work.

You'll have to use a type which can be implicitly constructed with std::nullptr_t. Something like this:

struct file_desc
{
  file_desc(int fd) : _desc(fd) {}
  file_desc(std::nullptr_t) : _desc(-1) {}

  operator int() {return _desc;}

  bool operator ==(const file_desc &other) const {return _desc == other._desc;}
  bool operator !=(const file_desc &other) const {return _desc != other._desc;}
  bool operator ==(std::nullptr_t) const {return _desc == -1;}
  bool operator !=(std::nullptr_t) const {return _desc != -1;}

  int _desc;
};

You can use that as the Deleter::pointer type.




回答2:


Can you do something simple like the following?

class unique_fd {
public:
    unique_fd(int fd) : fd_(fd) {}
    unique_fd(unique_fd&& uf) { fd_ = uf.fd_; uf.fd_ = -1; }
    ~unique_fd() { if (fd_ != -1) close(fd_); }

    explicit operator bool() const { return fd_ != -1; }

private:
    int fd_;

    unique_fd(const unique_fd&) = delete;
    unique_fd& operator=(const unique_fd&) = delete;
};

I do not see why you had to use unique_ptr, which is designed to manage pointers.




回答3:


A complete sample:

#include <memory>
#include <unistd.h>
#include <fcntl.h>

template<class T, T nullvalue, class D, D d> // auto d works in new compilers.
struct generic_delete
{
    class pointer
    {
        T t;
    public:
        pointer(T t) : t(t) {}
        pointer(std::nullptr_t = nullptr) : t(nullvalue) { }
        explicit operator bool() { return t != nullvalue; }
        friend bool operator ==(pointer lhs, pointer rhs) { return lhs.t == rhs.t; }
        friend bool operator !=(pointer lhs, pointer rhs) { return lhs.t != rhs.t; }
        operator T() { return t; }
    };
    void operator()(pointer p)
    {
        d(p);
    }
};
using unique_fd = std::unique_ptr<void, generic_delete<int, -1, decltype(&close), &close>>;
static_assert(sizeof(unique_fd) == sizeof(int), "bloated unique_fd");

int main()
{
    unique_fd fd1(open("fd.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR));
    write(fd1.get(), "hello\n", 6);
}



回答4:


The open source Android Framework defines a unique_fd class that might meet your needs: https://android.googlesource.com/platform/system/core/+/c0e6e40/base/include/android-base/unique_fd.h




回答5:


I would suggest using shared_ptr rather than unique_ptr to manage the life time of int handles because the shared ownership semantics are usually a better fit, and because the type of the deleter is erased. You need the following helper:

namespace handle_detail
{
    template <class H, class D>
    struct deleter
    {
        deleter( H h, D d ): h_(h), d_(d) { }
        void operator()( H * h ) { (void) d_(h_); }
        H h_;
        D d_;
    };
}

template <class H,class D>
std::shared_ptr<H const>
make_handle( H h, D d )
{
    std::shared_ptr<H> p((H *)0,handle_detail::deleter<H,D>(h,d));
    return std::shared_ptr<H const>(
        p,
        &std::get_deleter<handle_detail::deleter<H,D> >(p)->h_ );
}

To use with a file descriptor:

int fh = open("readme.txt", O_RDONLY); // Check for errors though.
std::shared_ptr<int const> f = make_handle(fh, &close);



回答6:


Making Nicol Bolas class more general:

template<class T=void*,T null_val=nullptr>
class Handle
    {
    public:
        Handle(T handle):m_handle(handle){}

        Handle(std::nullptr_t):m_handle(null_val){}

        operator T(){return m_handle;}

        bool operator==(const Handle& other) const
            {return other.m_handle==m_handle;}

    private:
        T m_handle;
    };

typedef Handle<int,-1> FileDescriptor;
typedef Handle<GLuint,0> GlResource; // according to http://stackoverflow.com/questions/7322147/what-is-the-range-of-opengl-texture-id
// ...

I am not sure if I should have default template parameter values or not.




回答7:


This solution is based on Nicol Bolas answer:

struct FdDeleter
{
    typedef int pointer;
    void operator()(int fd)
    {
        ::close(fd);
    }
};
typedef std::unique_ptr<int, FdDeleter> UniqueFd;

It's short, but you have to avoid to compare UniqueFd instance with nullptr and use it as boolean expression:

UniqueFd fd(-1, FdDeleter()); //correct
//UniqueFd fd(nullptr, FdDeleter()); //compiler error
if (fd.get() != -1) //correct
{
    std::cout << "Ok: it is not printed" << std::endl;
}
if (fd) //incorrect, avoid
{
    std::cout << "Problem: it is printed" << std::endl;
}
if (fd != nullptr) //incorrect, avoid
{
    std::cout << "Problem: it is printed" << std::endl;
}
return 1;


来源:https://stackoverflow.com/questions/15756960/using-unique-ptr-to-control-a-file-descriptor

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