Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using unique_ptr to control a file descriptor

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?

like image 463
Shachar Shemesh Avatar asked Apr 02 '13 05:04

Shachar Shemesh


2 Answers

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); });
}
like image 122
harel wallach Avatar answered Sep 20 '22 07:09

harel wallach


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

like image 24
Paul Crowley Avatar answered Sep 18 '22 07:09

Paul Crowley