Using unique_ptr to control a file descriptor

A complete sample:

#ifdef _MSC_VER 
#define _CRT_NONSTDC_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <io.h>
#else
#include <unistd.h>
#endif

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

template<auto nullvalue, auto delete_>
class unique
{
    using T = decltype(nullvalue);

    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()(T p)
        {
            delete_(p);
        }
    };
public:
    using type = std::unique_ptr<struct not_used, generic_delete>;
};

int main()
{

    using unique_fd = unique<-1, close>::type;
    static_assert(sizeof(unique_fd) == sizeof(int), "bloated unique_fd");
    unique_fd fd1(open("fd.txt", O_WRONLY | O_CREAT | O_TRUNC));
    write(fd1.get(), "hello\n", 6);
}

Found an answer at cppreference.com. Look in the examples code:

    void close_file(std::FILE* fp) { std::fclose(fp); }
    ...
    {
      std::unique_ptr<std::FILE, decltype(&close_file)> fp(std::fopen("demo.txt",                                                             
                                                                      "r"), 
                                                           &close_file);
      if(fp)     // fopen could have failed; in which case fp holds a null pointer
         std::cout << (char)std::fgetc(fp.get()) << '\n';
    }// fclose() called here, but only if FILE* is not a null pointer
     // (that is, if fopen succeeded)

Tried it in vs2019 and it works! Also tried it with member and lambda:

FileTest.h:

class A
{
  std::unique_ptr<std::FILE, std::function<void(std::FILE*)>> fp;
}

FileTest.cpp

void A::OpenFile(const char* fname)
{
    fp = std::unique_ptr < std::FILE, std::function<void(std::FILE*)>>(
        std::fopen(fname, "wb"),
        [](std::FILE * fp) { std::fclose(fp); });
}

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.


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.