HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMajor

shared_ptr and FILE for wrapping cstdio (update: also dlfcn.h)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
fileupdatedlfcncstdioshared_ptralsoforwrappingand

Problem

Even in the presence of `, there may be reason for using the file interface. I was wondering if wrapping a FILE* into a shared_ptr would be a useful construction, or if it has any dangerous pitfalls:

#include 
#include 

std::shared_ptr make_file(const char * filename, const char * flags)
{
  std::FILE * const fp = std::fopen(filename, flags);
  return fp ? std::shared_ptr(fp, std::fclose) : std::shared_ptr();
}

int main()
{
  auto fp = make_file("hello.txt", "wb");
  fprintf(fp.get(), "Hello world.");
}


Update: I just realized that it is not allowed to
fclose a null pointer. I modified make_file accordingly so that in the event of failure there won't be a special deleter.

Second update: I also realized that a
unique_ptr might be a more suitable than shared_ptr. Here is a more general approach:

typedef std::unique_ptr unique_file_ptr;
typedef std::shared_ptr shared_file_ptr;

static shared_file_ptr make_shared_file(const char * filename, const char * flags)
{
  std::FILE * const fp = std::fopen(filename, flags);
  return fp ? shared_file_ptr(fp, std::fclose) : shared_file_ptr();
}

static unique_file_ptr make_file(const char * filename, const char * flags)
{
  return unique_file_ptr(std::fopen(filename, flags), std::fclose);
}


Edit. Unlike
shared_ptr, unique_ptr only invokes the deleter if the pointer is non-zero, so we can simplify the implementation of make_file.

Third Update: It is possible to construct a shared pointer from a unique pointer:

unique_file_ptr up = make_file("thefile.txt", "r");
shared_file_ptr fp(up ? std::move(up) : nullptr);  // don't forget to check


Fourth Update: A similar construction can be used for
dlopen()/dlclose()`:

#include 
#include 

typedef std::unique_ptr unique_library_ptr;

static unique_library_ptr make_library(const char * filename, int flags)
{
  return unique_library_ptr(dlopen(filename, flags), dlclose);
}

Solution

I should start with the fact that I don't entirely agree with the widespread belief that "explicit is better than implicit". I think in this case, it's probably at least as good to have a class that just implicitly converts to the right type:

class file { 
    typedef FILE *ptr;

    ptr wrapped_file;
public:
    file(std::string const &name, std::string const &mode = std::string("r")) : 
        wrapped_file(fopen(name.c_str(), mode.c_str()))    
    { }

    operator ptr() const { return wrapped_file; }

    ~file() { if (wrapped_file) fclose(wrapped_file); }
};


I haven't tried to make this movable, but the same general idea would apply if you did. This has (among other things) the advantage that you work with a file directly as a file, rather than having the ugly (and mostly pointless) .get() wart, so code would be something like:

file f("myfile.txt", "w");

if (!f) {
   fprintf(stderr, "Unable to open file\n");
   return 0;
}

fprintf(f, "Hello world");


This has a couple of advantages. The aforementioned cleanliness is a fairly important one. Another is the fact that the user now has a fairly normal object type, so if they want to use overloading roughly like they would with an ostream, that's pretty easy as well:

file &operator<<(file &f, my_type const &data) { 
    return data.write(f);
}

// ...

file f("whatever", "w");
f << someObject;


In short, if the user wants to do C-style I/O, that works fine. If s/he prefers to do I/O more like iostreams use, a lot of that is pretty easy to support as well. Most of it is still just syntactic sugar though, so it generally won't impose any overhead compare to using a FILE * directly.

Code Snippets

class file { 
    typedef FILE *ptr;

    ptr wrapped_file;
public:
    file(std::string const &name, std::string const &mode = std::string("r")) : 
        wrapped_file(fopen(name.c_str(), mode.c_str()))    
    { }

    operator ptr() const { return wrapped_file; }

    ~file() { if (wrapped_file) fclose(wrapped_file); }
};
file f("myfile.txt", "w");

if (!f) {
   fprintf(stderr, "Unable to open file\n");
   return 0;
}

fprintf(f, "Hello world");
file &operator<<(file &f, my_type const &data) { 
    return data.write(f);
}

// ...

file f("whatever", "w");
f << someObject;

Context

StackExchange Code Review Q#4679, answer score: 28

Revisions (0)

No revisions yet.