Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to read a whole file opened in text mode into a string variable

Tags:

c++

file

These are the things I cannot change:

  • The language is C++
  • However, the file is opened with the good old fopen()
  • The file is not opened in binary mode

This is what I have to do:

  • Write a function that loads the entire file into a std::string. Lines should be separated only by \n and not other variants.

This is what I did:

string ReadWhole()
{
    Seek(0);
    char *data = new char[GetSize()];

    if (1 != fread(data, GetSize(), 1, mFile))
        FATAL(Text::Format("Read error: {0}", strerror(errno)));

    string ret(data, GetSize());
    delete[] data;
    return ret;
}

For reference, this is GetSize, but it simply returns the size of the file (cached):

int GetSize()
{
    if (mFileSize)
        return mFileSize;

    const int current_position = ftell(mFile);
    fseek(mFile, 0, SEEK_END);
    mFileSize = ftell(mFile);
    fseek(mFile, current_position, SEEK_SET);

    return mFileSize;
}

This is the problem

fread() fails because the file has \r\n line endings and they count as only 1 character instead of 2, so it tries to read more than the characters in the file.

I could fix it with fgets but I was wondering if there was a better way. Thanks.

like image 921
hbyy Avatar asked Dec 12 '10 16:12

hbyy


2 Answers

After fread returns that it was unable to read the requested number of bytes, you should simply check ferror(mFile). If it's 0 (false) then fread just stopped at the end of the file and you should not tread this as an error. You should switch the two arguments though so you can get the number of bytes actually read:

size_t number_of_bytes_read = fread(data, 1, GetSize(), mFile);
like image 180
R.. GitHub STOP HELPING ICE Avatar answered Sep 21 '22 13:09

R.. GitHub STOP HELPING ICE


There is a trivial, idiomatic way to perform this operation.

#include <string>
#include <fstream>
#include <sstream>
std::string load_file ( const std::string& path ) 
{
    std::ostringstream contents;
    std::ifstream file(path);
    if ( !file.is_open() ) {
        // process error.
    }
    contents << file.rdbuf();
    return (contents.str());
}

Note: this function does not use seeking to obtain the size (in bytes) of the input file. This has the down-side of (few) re-allocations to increase the buffer as more input is made available. It has the up-side of working with other std::istream implementations, which might not be able to provide the contents' size ahead of time (i.e. reading from a socket).

Edit: because your requirements state use of FILE*, which is already opened and you cannot change, you can implement a std::streambuf implementation that uses an existing FILE* to allow re-use of high-level std::istream and std::ostream operations.

A sample implementation is available right here, on StackOverflow.

P.S.: If you've never used non-standard-library stream buffer implementations, here's a quick overview of how to write the function given the implementation I pointed to.

#include <string>
#include <istream>
#include <sstream>
#include "FILEbuf.h"
std::string load_file ( ::FILE * opened_c_file ) 
{
    FILEbuf buffer(opened_c_file);
    std::istream file(&buffer);
    std::ostringstream contents;
    contents << file.rdbuf();
    return (contents.str());
}
like image 29
André Caron Avatar answered Sep 23 '22 13:09

André Caron