patterncppMajor
shared_ptr and FILE for wrapping cstdio (update: also dlfcn.h)
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:
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
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:
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
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.