views:

740

answers:

3

Hello,

I need to read a whole file into memory and place it in a C++ std::string.

If I were to read it into a char, the answer would be very simple:

std::ifstream t;
int lenght;
t.open("file.txt");      // open input file
t.seekg(0, std::ios::end);    // go to the end
length = t.tellg();           // report location (this is the lenght)
t.seekg(0, std::ios::beg);    // go back to the beginning
buffer = new char[length];    // allocate memory for a buffer of appropriate dimension
t.read(buffer, length);       // read the whole file into the buffer
t.close();                    // close file handle

// ... do stuff with buffer here ...

Now, I want to do the exact same thing, but using a std::string instead of a char. I want to avoid loops, i. e., I don't want to:

std::ifstream t;
t.open("file.txt");
std::string buffer;
std::string line;
while(t){
std::getline(t, line);
// ... append line to buffer and go on
}
t.close()

any ideas?

+16  A: 

You can make a streambuf iterator out of the file and initialize the string with it:

#include <string>
#include <fstream>
#include <streambuf>

std::ifstream t("file.txt");
std::string str((std::istreambuf_iterator<char>(t)),
                 std::istreambuf_iterator<char>());

Not sure where you're getting the t.open("file.txt", "r") syntax from. As far as I know that's not a method that std::ifstream has. It looks like you've confused it with C's fopen.

Edit: Also note the extra parentheses around the first argument to the string constructor. These are essential. They prevent the problem known as the "most vexing parse", which in this case won't actually give you a compile error like it usually does, but will give you interesting (read: wrong) results.

Following KeithB's point in the comments, here's a way to do it that allocates all the memory up front (rather than relying on the string class's automatic reallocation):

#include <string>
#include <fstream>
#include <streambuf>

std::ifstream t("file.txt");
std::string str;

t.seekg(0, std::ios::end);   
str.reserve(t.tellg());
t.seekg(0, std::ios::beg);

str.assign((std::istreambuf_iterator<char>(t)),
            std::istreambuf_iterator<char>());
Tyler McHenry
open is definitely a method of ifstream, however the 2nd parameter is wrong. http://www.cplusplus.com/reference/iostream/ifstream/open/
Joe
Right. I was saying that `ifstream` doesn't have a method with the signature `open(const char*, const char*)`
Tyler McHenry
This is just making the explicit loop implicit. Since the iterator is a forward iterator, it will be read one character at a time. Also, since there is no way for the string constructor to know the final length, it will probably lead to several allocations and copies of the data.
KeithB
Yep, I am starting off with C++ and I'm still quite illiterate. Thanks for the answer, though, it is exactly what I needed. +1.
Arrieta
no second parameter is required - ifstreams are input streams
anon
@KeithB If efficiency is important, you could find the file length the same was as in the `char*` example and call `std::string::reserve` to preallocate the necessary space.
Tyler McHenry
@KeithB: Of course, the `read()` method undoubtedly has lots of looping going on. The question is not whether it loops but where and how explicitly.
David Thornley
In the `str.assign()` approach the first argument's parentheses are unnecessary, because it can't parse as a declaration.
wilhelmtell
Note that the file may be longer than the string. If your OS uses <CR><LF> (two `char`s) as a line separator, the string will use '\n' (one `char`). Text streams do conversions to and from '\n' to the underlying representation.
Adrian McCarthy
@Adrian `'\n'` is merely a portable way of specifying newline in C code. Down below the compiler will still translate `'\n'` to what's appropriate for a newline for the compiler's operating system.
wilhelmtell
+1  A: 

I don't think you can do this without an explicit or implicit loop, without reading into a char array (or some other container) first and ten constructing the string. If you don't need the other capabilities of a string, it could be done with vector<char> the same way you are currently using a char *.

KeithB
+3  A: 

There are a couple of possibilities. One is like to use a stringstream as a go-between:

std::ifstream t("file.txt");
std::stringstream buffer;
buffer << t.rdbuf();

Now the contents of "file.txt" is available in a string as t.str().

Another possibility (though I certainly don't like it as well) is much more like your original:

std::ifstream t("file.txt");
t.seekg(0, std::ios::end);
size_t size = t.tellg();
std::string buffer(size, ' ');
t.seekg(0);
t.read(&buffer[0], size); 

Officially, this isn't required to work under the current standard (string isn't required to store data contiguously) but in fact it works with all known implementations, and the next standard will require contiguous storage, so it'll be guaranteed to work.

As to why I don't like the latter: first, because it's longer and harder to read. Second, because it requires that you initialize the contents of the string with data you don't care about, then immediately write over that data (though the time to initialize is usually trivial compared to the reading, so even though it probably doesn't matter, it just feels wrong). Third, in a text file, position X in the file doesn't necessarily mean you'll have read X characters when you reach that point -- it's not required to take into account things like line-end translations.

Jerry Coffin