How do I read a file into a std::string
, i.e., read the whole file at once?
Text or binary mode should be specified by the caller. The solution should be standard-compliant, portable and efficient. It should not needlessly copy the string's data, and it should avoid reallocations of memory while reading the string.
One way to do this would be to stat the filesize, resize the std::string
and fread()
into the std::string
's const_cast<char*>()
'ed data()
. This requires the std::string
's data to be contiguous which is not required by the standard, but it appears to be the case for all known implementations. What is worse, if the file is read in text mode, the std::string
's size may not equal the file's size.
A fully correct, standard-compliant and portable solutions could be constructed using std::ifstream
's rdbuf()
into a std::ostringstream
and from there into a std::string
. However, this could copy the string data and/or needlessly reallocate memory.
void slurp(std::string& data, bool is_binary)
One way is to flush the stream buffer into a separate memory stream, and then convert that to std::string
:
std::string slurp(std::ifstream& in) {
std::ostringstream sstr;
sstr << in.rdbuf();
return sstr.str();
}
This is nicely concise. However, as noted in the question this performs a redundant copy and unfortunately there is fundamentally no way of eliding this copy.
The only real solution that avoids redundant copies is to do the reading manually in a loop, unfortunately. Since C++ now has guaranteed contiguous strings, one could write the following (≥C++14):
auto read_file(std::string_view path) -> std::string {
constexpr auto read_size = std::size_t{4096};
auto stream = std::ifstream{path.data()};
stream.exceptions(std::ios_base::badbit);
auto out = std::string{};
auto buf = std::string(read_size, '\0');
while (stream.read(& buf[0], read_size)) {
out.append(buf, 0, stream.gcount());
}
out.append(buf, 0, stream.gcount());
return out;
}