Weird behavior with ifstreams and rdbuf() - c++

I've noticed that using .rdbuf() on an ifstream seems to change it somehow. The following code should show the problem.
#include <fstream>
#include <iostream>
using namespace std;
int main(int argc, const char * argv[]) {
ifstream ifs("Sample.csv");
cout << "Reading buffer: " << endl;
cout << ifs.rdbuf(); // Outputs buffer as expected
cout << "Reading buffer again: " << endl;
cout << ifs.rdbuf(); // Returns nothing
return 0;
}
The reason this is bothering me is that I'm currently trying to copy the contents of one text file into another using ofstream ofs; ofs << ifs.rdbuf(). This works fine but makes reading from ifs using getline(ifs, str) fail, effectively "breaking" the stream.

This isn't particularly "weird"; it's the same stream behaviour you see every day. rdbuf isn't like std::stringstream::str() and it isn't magic — it's a pointer to the buffer, that your cout is then reading from just as you would read from the original stream yourself:
std::stringstream ss("1");
int x;
if (ss >> x)
cout << x;
if (ss >> x) // doesn't work a second time; "1" is already extracted
cout << x;
As your stream is a file stream, you can seek it back to the beginning to start from scratch (which will inherently do the same to its underlying buffer).

ifs.rdbuf() returns a pointer to the ifs's corresponding stream buffer object. Sending it to std::cout via << overload pulls information from the stream until the end of the buffer is reached (eof). Calling .rdbuf() again returns "nothing" because there's nothing to read at the end of the buffer. The buffer seek position be explicitly reset to zero by calling ifs.seekg (0);.

Related

How to write string stream to ofstream?

I am trying to write a stringstream into a file but it not working.
int main() {
std::stringstream stream;
stream << "Hello world";
cout << stream.rdbuf()<<endl;//prints fine
std::ofstream p{ "hi.txt" };
p << stream.rdbuf();//nothing is writtten
p.close();
std::ifstream ip{ "hi.txt" };
std::string s;
ip >> s;
cout << s;//nothing printed
p.close();
return 0;
}
This answer here follows the same process. But it's not working in my case.
Internally, the stringstream maintain a read pointer/offset into its data buffer. When you read something from the stream, it reads from the current offset and then increments it.
So, cout << stream.rdbuf() reads the entire stream's data, leaving the read pointer/offset at the end of the stream. Thus, there is nothing left for p << stream.rdbuf() to read afterwards. In order to do that, you have to seek the read pointer/offset back to the beginning of the steam, eg:
cout << stream.rdbuf() << endl;
stream.clear(); // resets the error state, particularly eofbit
stream.seekg(0);
p << stream.rdbuf();
The C++ file streams use input and output pointers to keep track of it's data buffer. When you write something it is written from the position where the current write pointer is or we can say the write pointer offset is added and then the data is simply written, after which the write pointer is incremented by that much value
The stream.rdbuf() function reads all the data in the current file and leaves the read pointer at the end of the file so when we run the same function again there is nothing to read
This can Simply be solved by using stream.seekg(0)
cout << stream.rdbuf() << endl;
stream.seekg(0); //This puts the pointer at the beginning of the file
p << stream.rdbuf(); //Should work'

edit: trouble checking if file is empty or not, what am I doing wrong?

Edit: changed my question to be more accurate of the situation
I'm trying to open up a text file (create it if it doesnt exist,open it if it doesnt). It is the same input file as output.
ofstream oFile("goalsFile.txt");
fstream iFile("goalsFile.txt");
string goalsText;
string tempBuffer;
//int fileLength = 0;
bool empty = false;
if (oFile.is_open())
{
if (iFile.is_open())
{
iFile >> tempBuffer;
iFile.seekg(0, iFile.end);
size_t fileLength = iFile.tellg();
iFile.seekg(0, iFile.beg);
if (fileLength == 0)
{
cout << "Set a new goal\n" << "Goal Name:"; //if I end debugging her the file ends up being empty
getline(cin, goalSet);
oFile << goalSet;
oFile << ";";
cout << endl;
cout << "Goal Cost:";
getline(cin, tempBuffer);
goalCost = stoi(tempBuffer);
oFile << goalCost;
cout << endl;
}
}
}
Couple of issues. For one, if the file exist and has text within it, it still goes into the if loop that would normally ask me to set a new goal. I can't seem to figure out what's happening here.
The problem is simply that you are using buffered IO streams. Despite the fact that they reference the same file underneath, they have completely separate buffers.
// open the file for writing and erase existing contents.
std::ostream out(filename);
// open the now empty file for reading.
std::istream in(filename);
// write to out's buffer
out << "hello";
At this point, "hello" may not have been written to disk, the only guarantee is that it's in the output buffer of out. To force it to be written to disk you could use
out << std::endl; // new line + flush
out << std::flush; // just a flush
that means that we've committed our output to disk, but the input buffer is still untouched at this point, and so the file still appears to be empty.
In order for your input file to see what you've written to the output file, you'd need to use sync.
#include <iostream>
#include <fstream>
#include <string>
static const char* filename = "testfile.txt";
int main()
{
std::string hello;
{
std::ofstream out(filename);
std::ifstream in(filename);
out << "hello\n";
in >> hello;
std::cout << "unsync'd read got '" << hello << "'\n";
}
{
std::ofstream out(filename);
std::ifstream in(filename);
out << "hello\n";
out << std::flush;
in.sync();
in >> hello;
std::cout << "sync'd read got '" << hello << "'\n";
}
}
The next problem you'll run into trying to do this with buffered streams is the need to clear() the eof bit on the input stream every time more data is written to the file...
Try Boost::FileSystem::is_empty which test if your file is empty. I read somewhere that using fstream's is not a good way to test empty files.

writing after reading using fstream

I am under the impression fstream objects in c++ can be used to both read and write, using the same stream.
I have successfully been able to first write to a stream and then read from it. If I however try to write to it again the file is not affected.
Here is a code example that successfully compiles on windows using MinGw:
int main()
{
std::string path="file.txt";
std::fstream fs(path.c_str());
int buffSize=100;
int bytesRead=0;
char* buffer=new char[buffSize];
fs.write("hello", 5);
fs.seekp(0, std::ios::beg);
fs.read(buffer, buffSize);
bytesRead=fs.gcount();
for(int i=0;i<bytesRead;i++) {std::cout << buffer[i];}
std::cout << "\n";
fs.clear();
fs.seekp(1, std::ios::beg);
fs.write("E", 1);
std::cout << "fail: " << fs.fail() << "\n";
delete[] buffer;
}
The initial content of "file.txt" was only:
AAAAAAA
And the program outputs:
helloAA
fail: 0
Looking at the file in a text editor after running the program shows that the final content is:
helloAA
The final writing of the "E" has not taken effect, why is this and how do I fix it?
EDIT:
I tried using fs.clear() before writing again as user 0x499602D2 suggested. Also added a line printing out whether the failbit or badbit has been set or not and updated the program output. The final file content stays the same however, the problem remains.
(more verbose answer from what I posted in comments on the question)
You need to call flush() on output stream objects (derived from ostream) in order for the data to actually be written on the output stream. More information on flush() is available on this c++ reference page.
This work in GCC 4.9.0 and VS2013.
Notes:
seekg is for move the read pointer
seekp is for move the write pointer
In the sample code in line fs.seekp(0, std::ios::beg); need to be seekg. There is no problem because the read pointer has not been move (there is no read until there).
Code:
#include <algorithm>
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[]) {
std::string path = "H:\\save.txt";
int buffSize = 100;
int bytesRead = 0;
char* buffer = new char[buffSize];
std::fstream fs(path.c_str());
fs.write("hello", 5);
fs.flush(); // flushing to disk file
fs.seekg(0, std::ios_base::beg); // moving the read pointer
fs.read(buffer, buffSize);
bytesRead = fs.gcount();
for (int i = 0; i < bytesRead; i++) {
std::cout << buffer[i];
}
std::cout << "\n";
fs.clear();
fs.seekp(1, std::ios::beg);
fs.write("E", 1);
fs.flush(); // flushing to disk file
std::cout << "fail: " << fs.fail() << "\n";
delete[] buffer;
return 0;
}
string data="";
string Newdata="New Data";
std::fstream output_file(fileName, ios::in| ios::out);
output_file >> data; //read Data
output_file.seekg( 0, ios::beg );//set point to zero
output_file<<Newdata<<"\n"; //write new Data
output_file.close();
once you read a file using fstream, tellg < read pointer > and tellp < write pointer > points to -1.
to be able to write again using fstream, just call fstream.clear() and it will reset read and write pointer to where it was before reading.
none of the solution posted above work but fstream.clear() works.

Stringstream don't copy new lines

Special characters disappear when I pass a string into a stringstream.
I tried this code which can directly be tested:
#include <iostream>
#include <sstream>
using namespace std;
int main(int argc, char* argv[]) {
string txt("hehehaha\n\t hehe\n\n<New>\n\ttest:\t130\n\ttest_end:\n<New_end>\n");
cout << txt << endl; // No problem with new lines and tabs
stringstream stream;
stream << txt;
string s;
while(stream >> s) {
cout << s; // Here special characters like '\n' and '\t' don't exist anymore.
}
cout << "\n\n";
return 0;
}
What can I do to overcome this?
Edit: I tried this:
stream << txt.c_str();
and it worked. But I don't know why...
basically, you are just printing it wrong, it should be:
cout << stream.str() << endl;
Some details. You are calling operator<<(string) which
overloads operator<< to behave as described in ostream::operator<<
for c-strings
The referred to behaviour is explained here:
(2) character sequence Inserts the C-string s into os. The terminating
null character is not inserted into os. The length of the c-string is
determined beforehand (as if calling strlen).
Strlen documentation says that the result is affected by nothing but
the terminating null-character
Indeed, strlen(tmp) in your examples outputs 55.
The stream, hence, gets "assigned" everything which comes up to the 55th character in your input string.
cout << stream.str() << endl;
will show you that this is indeed what happens.
A parenthesis: you can modify the behaviour of the stream << txt line by means of setting/unsetting flags, as in
stream.unsetf ( std::ios::skipws );
which you should try out.
The statement
while(stream >> s)
Is the problem, it gives you one token on each call, using white spaces for splitting and therefor ignoring them.

C++ Fstream Only Prints One Word

This is a very strange issue. I'm trying to print a large text file, it's a Wikipedia entry. It happens to be the page on Velocity. So, when I tell it to print the file, it prints "In", when it should print "In physics, velocity is etc, etc etc".
Here's the code I'm using to print out:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream wiki;
wiki.open("./wiki/velocity.txt");
char* wikiRead;
wiki >> wikiRead;
cout << wikiRead << endl;
wiki.close();
}
Please help.
wiki >> wikiRead;
The default delimiter for stream is space, so when the stream encounters a space, it simply stops reading, that is why it reads only one word.
If you want the stream to read all words, the you've to use a loop as:
char* wikiRead = new char[1024]; //must allocate some memory!
while(wiki >> wikiRead)
{
cout << wikiRead << endl;
}
wiki.close();
delete []wikiRead; //must deallocate the memory
This will print all the words in the file, each on a new line. Note if any of the word in the file is more than 1024 character long, then this program would invoke undefined behavior, and the program might crash. In that case, you've to allocate a bigger chunk of memory.
But why use char* in the first place? In C++, you've better choice: Use std::string.
#include<string>
std::string word;
while(wiki >> word)
{
cout << word << endl;
}
wiki.close();
Its better now.
If you want to read line-by-line, instead of word-by-word, then use std::getline as:
std::string line;
while(std::getline(wiki, line))
{
cout << line << endl;
}
wiki.close();
This will read a complete line, even if the line contains spaces between the words, and will print each line a newline.
You ask the stream to read the (binary) value of a pointer (probably 4 bytes, depending on your machine architecture), then you ask it to print the text pointed to by those 4 bytes!
I wonder why you ignored the compiler warning (most of the modern compiler warns you about using uninitialized variables). How about this?
ifstream wiki;
wiki.open("./wiki/velocity.txt");
char wikiRead[255];
wiki >> wikiRead;
cout << wikiRead << endl;
wiki.close();
Alternatively I'd suggest you to use string object with getline to get a single line of text.
string str;
getline(wiki, str);
The >> operator applied to a char * reads only one word. Moreover, you're reading into an uninitialized pointer, which is not valid. Usually std::string, not char *, is used for string processing in C++.
If you only want to print the file's contents, you can hook the file's buffer directly to std::cout:
int main() {
std::ifstream wiki("./wiki/velocity.txt");
std::cout << wiki.rdbuf() << '\n';
}
If you want to put the contents into an automatically-allocated string, use std::getline with the delimiter disabled.
int main() {
std::ifstream wiki("./wiki/velocity.txt");
std::string wiki_contents;
getline( wiki, wiki_contents, '\0' /* do not stop at newline */ );
std::cout << wiki_contents << '\n'; // do something with the string
}
Since you want to read a large file, reading it block by block is a better way.
ifstream wiki;
wiki.open("./wiki/velocity.txt");
const int buf_size = 1024;
char* wikiRead = 0;
int cnt = 1;
do
{
wikiRead = realloc( wikiRead, bufsize*cnt );
wiki.Read( wikiRead + (bufSize*(cnt-1)), buf_size ); //appends to reallocated memory
cnt++;
}while( !wiki.eof())
wikiRead[(bufSize*(cnt-2)) + wiki.gcount() + 1] = '\0'; // null termination.
wiki.Close();
cout << wikiRead;
delete[] wikiRead;
The operator>> is designed to only read one word at a time. If you want to read lines, use getline.
#include <iostream>
#include <fstream>
#include<string>
using namespace std;
int main()
{
ifstream wiki;
wiki.open("./wiki/velocity.txt");
string wikiRead;
while (getline(wiki, wikiRead))
{
cout << wikiRead << endl;
}
wiki.close();
}