Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::ofstream with std::ate not opening at end

Tags:

c++

c++11

fstream

I am trying to open a file for output and append to it. After appending to it, I want to move my output position somewhere else in the file and overwrite existing data. As I understand it, std::ios_base::app will force all writes to be at the end of the file which is not what I want to do. As such, I believe std::ios_base::ate is the correct flag to pass to std::ofstream::open(). However, it seems to be not working as expected:

// g++ test.cpp
// clang++ test.cpp
// with and without -std=c++11
#include <iostream>
#include <fstream>

int main() {
    std::streampos fin, at;
    {
        std::ofstream initial;
        initial.open("test", std::ios_base::out | std::ios_base::binary);
        if ( not initial.good() ) {
            std::cerr << "initial bad open" << std::endl;
            return 1;
        }
        int b = 100;
        initial.write((char*)&b, sizeof(b));
        initial.flush();
        if ( not initial.good() ) {
            std::cerr << "initial write bad" << std::endl;
            return 1;
        }
        fin = initial.tellp();
    }
    {
        std::ofstream check;
        check.open("test", std::ios_base::out | std::ios_base::binary | std::ios_base::ate);
        if ( not check.good() ) {
            std::cerr << "check bad open" << std::endl;
            return 1;
        }
        at = check.tellp();
        if ( fin != at ) {
            std::cerr << "opened at wrong position!\nfin:\t" << fin << "\n" << "at:\t" << at << std::endl;
            return 1;
        }
        int bb = 200;
        check.write((char*)&bb, sizeof(bb));
        check.flush();
        if ( not check.good() ) {
            std::cerr << "check write bad" << std::endl;
            return 1;
        }
        at = check.tellp();
    }
    if ( (fin + std::streampos(sizeof(int))) != at ) {
        std::cerr << "overwrite?\nfin:\t" << fin << "\n" << "at:\t" << at << std::endl;
        return 1;
    }
    return 0;
}

In particular, it seems that std::ios_base::ate does not move the initial output pointer to the end with the example seen above. Obviously this would result in the first write overwriting the beginning of the file (which is what caused my trouble).

It seems that either the implementation is incorrect or else cplusplus.com is incorrect ("The output position starts at the end of the file.") and cppreference.com is ambiguous ("seek to the end of stream immediately after open": which stream?).

There is obviously an easy workaround: just use stream.seekp(0, std::ios_base::end).

So my question is thus: is my code incorrect? Is the implementation incorrect? Are the reference sites incorrect? Any insight would be appreciated.

like image 332
inetknght Avatar asked Mar 12 '15 00:03

inetknght


1 Answers

As you can see from the following chart in N4296 [filebuf.members]

file io

The combination binary | out will open the file in the stdio equivalent of "wb", which will truncate to zero length or create binary file for writing (N1570 7.21.5.2).

As counterintuitive as it sounds for an ofstream, you will need to add the in flag if you don't want your file to be truncated, or app if you want to avoid truncation and seek to the end of the file on each write.

Bonus tip: Unlike fstream, ifstream and ofstream will automatically or std::ios_base::in and std::ios_base::out respectively with any flags you provide to the constructor or to open. You can also use the object itself to access the flags:

std::ofstream check("test", check.in | check.binary | check.ate);

The checks for good can also be shortened to if (!initial) etc.

like image 188
user657267 Avatar answered Sep 20 '22 15:09

user657267