15

What is the proper set of I/O flags for a std::fstream, where I want to be able to read from and write to the file, without truncating the file if it exists, but creating it if it does not?

I've tried

std::ios::binary | std::ios::in | std::ios::out
std::ios::binary | std::ios::in | std::ios::out | std::ios::ate

but neither of these create the file if it does not already exist.

I don't want std::ios::app, because I also need to be able to seek around the file at will, with both the get and put cursors.

One workaround, I suppose, would be to instantiate an std::ofstream first, then immediately close it and open the stream I really want, but that seems messy if it can be avoided with a single stream object.

SF.
  • 13,549
  • 14
  • 71
  • 107
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055

3 Answers3

4

At this time, I'm concluding that std::ios::in outright prevents this, and that I must use the workaround.

So:

if (!std::ostream(path.c_str()))
   throw std::runtime_error("Could not create/open file");

std::fstream fs(path.c_str(), std::ios::binary | std::ios::in | std::ios::out);
if (!fs)
   throw std::runtime_error("Could not open file");

// ... use `fs`
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • You do not need failure checking or reporting for the creation attempt. Also, I think you don't need to use `c_str()` with C++11 and later. It's possible that Boost filesystem does this thing for you (I don't know), if so then you only have to wait till C++17. ;-) – Cheers and hth. - Alf Jul 17 '15 at 19:45
  • @Cheersandhth.-Alf as-tagged (c++03), seems the reason for the `c_str()`, though you're clearly right if anyone reading this is going to do it with C++11 or later. Wasn't `std::string` construction included in 03x, btw? – WhozCraig Jul 17 '15 at 19:47
  • @WhozCraig: No, the only new thing in C++03 was value initialization. – Cheers and hth. - Alf Jul 17 '15 at 19:51
  • @Cheersandhth.-Alf ah..then all those `c_str` indeed make sense when coupled with that tag. thanks for the info! – WhozCraig Jul 17 '15 at 19:51
  • @Cheersandhth.-Alf: I realise I don't _need_ to check the first stream, but I'd rather have the more specific error message if creating the file fails. And, yeah, C++03 so the `.c_str()` is needed. :) – Lightness Races in Orbit Jul 17 '15 at 20:29
  • 1
    @Lightness Races in Orbit, I believe you forgot to add `std::ios::binary | std::ios::app` when you called the `std::ostream` constructor. Without those flags the file will be truncated in case it already exists, which is something you said you did not want. And neither did I when I stumbled upon this :) – user1593842 Jun 15 '20 at 22:43
2

An investigation, from a Linux perspective (though much of this likely applies to other Unices):

At the syscall layer, you want open(O_RDWR | O_CREAT, 0666) (but not O_TRUNC or O_APPEND or a bunch of other flags, though arguably all files should be opened with O_CLOEXEC | O_LARGEFILE, but that's beside the point)

At the libc layer, there is no standard mode string that implies O_CREAT without O_TRUNC. However, you could use open followed by fdopen.

At the C++ library level, there is no standard way to pass the desired flags. However, using implementation-specific classes/functions or third-party libraries, it is possible; see How to construct a c++ fstream from a POSIX file descriptor?


Personally, I tend to do all I/O at the C or even syscall level, since the API is a lot nicer and it's more predictable. For input/output of class instances, I have my own templates.

o11c
  • 15,265
  • 4
  • 50
  • 75
-1

Taking std::ios::binary as read, the remaining openmode probably you require is:

std::ios::in | std::ios::app

It's effect is as if to open the file with:

std::fopen(filename,"a+")

and the effect of that is:

  • open or, if it does not exist, create the file for reading and writing
  • write data at the end of the file.

If you open a file as an std::fstream with this openmode, it is not truncated if it exists. You may read from the file wherever the fstream's tellg()\tellp() pointer points, provided there is something there to read, and you can position that pointer with the stream's seekg()\seekp() for reading. However, all writes will be appended to the end of the file.

This openmode will therefore fit your bill unless you need to perform writes into existing data.

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182