4

Is there a way to obtain the FILE* handle of an opened file in a modern C++ way?.. I want to avoid using std::fopen from <cstdlib> as I've been warned that it's potentially unsafe.

I tried checking if there's a way to retrieve FILE* from fstream but it seems there isn't a way to do that.

cigien
  • 57,834
  • 11
  • 73
  • 112
Jack Avante
  • 1,405
  • 1
  • 15
  • 32
  • 4
    The C++ way doesn't involve FILE* at all. – sweenish Jan 23 '21 at 19:10
  • So there is no way to get FILE* in C++?.. Cause there are still come functions and even boost libraries that take advantage of it – Jack Avante Jan 23 '21 at 19:14
  • I'd be curious about what functions you're referring to, and whether the boost libraries you're referring to have been superseded by something in the Standard Library. You can naturally still use FILE*, but you're going to be bringing in those C headers to do it. – sweenish Jan 23 '21 at 19:18
  • `FILE*` is from C, not C++. The C++ way to deal with files is to use `std::(i|o)fstream` instead. – Remy Lebeau Jan 23 '21 at 19:20
  • 2
    Re: “warned that it’s potentially unsafe” — what’s “unsafe” about it? How do C programmers deal with it being “unsafe”? – Pete Becker Jan 23 '21 at 19:23

2 Answers2

3

I want to avoid using std::fopen from <cstdlib> as I've been warned that it's potentially unsafe.

There is nothing "unsafe" about fopen(). It is part of the C++ standard by means of the C standard.

What they probably said is that using a RAII object is best to manage the resource (the file handle), in particular if you're using exceptions in your program.

I tried checking if there's a way to retrieve FILE* from fstream but it seems there isn't a way to do that.

Not an easy, completely portable one. See questions like this one.

If you really want a FILE*, use the C API.

Acorn
  • 24,970
  • 5
  • 40
  • 69
1

Some fun.

namespace file {
  template<auto f>
  using constant = std::integral_constant< std::decay_t<decltype(f)>, f >;

  using file_ptr = std::unique_ptr<std::FILE, constant<std::fclose>>;

  FILE* unwrap( file_ptr const& ptr ) { return ptr.get(); }
  template<class T> requires (!std::is_same_v< file_ptr, std::decay_t<T> >)
  T&& unwrap( T&& t ) { return std::forward<T>(t); }
  file_ptr wrap( FILE* ptr ) { return file_ptr(ptr); }
  template<class T> requires (!std::is_same_v< file_ptr, std::decay_t<T> >)
  T&& wrap( T&& t ) { return std::forward<T>(t); }

  template<auto f>
  auto call = [](auto&&...args){ return wrap( f( unwrap(decltype(args)(args))... ) ); };
  // most FILE* operations can be rebound as follows:
  auto open = call<std::fopen>;
  auto close = call<std::fclose>;
  auto getc = call<std::fgetc>;
  auto putc = call<std::fputc>;
  // any one that works with raw buffers (like std::fread or std::fwrite) needs more work
}

the core of this is that a unique_ptr<FILE, thing that calls fclose> is a decent quality FILE* C++ wrapper.

Then I added some machinery that maps fgetc and similar functions over to ones that operate on file::file_ptr by wrapping/unwrapping them.

now you can do:

auto pFile = file::open("hello.txt", "r");

Live example.

auto pFile = file::open("hello.txt", "r");
auto pFileOut = file::open("world.txt", "w");
if (pFile)
{
    while(auto c = file::getc(pFile))
    {
        file::putc(c, pFileOut);
    }
}

a more advanced version would take the arguments from a function pointer, toss them in a tuple, map them to-from tuples of munged arguments.

So

char*, int

would map to a std::span<char>.

The call helper instead of the naive stuff above would take the munged arguments, map them to tuples of C style arguments, concatinate the tuples together, and std::apply the C function. Then map the return value.

call with a bit of work could even have a fixed signature instead of auto&&..., so IDEs could give hints.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524