I am writing a unit test for file read using qtestlib, C++ (clang LLVM version 8.0). I have the following code for reading a file line by line.
std::ifstream infile;
try {
infile.open(path.c_str());
std::ios_base::iostate exceptionMask = infile.exceptions() | std::ios::failbit;
infile.exceptions(exceptionMask);
} catch (std::ios_base::failure& e) {
// print the exception
qDebug() << "Exception caught: " << QString::fromStdString(e.what());
}
try {
std::string line;
while (std::getline(infile, line)) {
// print the line
qDebug() << QString::fromStdString(line);
}
} catch (std::ios_base::failure& e) {
qDebug() << "Exception caught: " << QString::fromStdString(e.what());
}
The issue:
The above code reads all the lines in the file and prints it. But after printing the last line, it throws an exception and prints the following,
Exception caught: "basic_ios::clear"
I followed many threads, but could not find the solution to this. Why am I getting this error?
After you have read and printed all the lines, the while (std::getline(infile, line)) will still try to read another line. If it fails totally - zero characters read - it sets failbit to signal its failure.
The odd part of the error message is that, despite its name, basic_ios::clear can be used to set the failure bit and will also throw an exception if you have enabled the same bit with exceptions.
Take a look on documentation of std::getline. Scetion about setting flags:
failbit
The input obtained could not be interpreted as a valid textual representation of an object of this type. In this case,
distr preserves the parameters and internal data it had before the call. Notice that some eofbit cases will also set failbit.
The last sentence is a bit fuzzy, but can explain observed behavior.
I did some experiments and found the pattern. First I've corrected your code this way:
try {
std::string line;
while (std::getline(infile, line)) {
// print the line
qDebug() << QString::fromStdString(line);
if (infile.eof()) {
return;
}
}
} catch (std::ios_base::failure& e) {
qDebug() << "Exception caught: " << QString::fromStdString(e.what());
}
Now if input file ends with empty line I get an exception, if last line doesn't end with "\n" return breaks a loop.
So falbit is set if you are trying to read stream which already reached end of stream.
Without "if" check you are always doing this reading and always get an exception.
For last line empty I have some clues, but have not idea how to explain it nicely. First have to check behavior of other platforms/compilers.
Related
I've been having a nightmare this evening trying to get some very simple I/O functionality going. As embarrassing as it is, I've had some great help from people on here!
My current issue is that I'm attempting to use ifstream.open() and it simply is not opening the file. This is confirmed by getline(ifstream,line); returning false on it's first call.
Here is a copy paste of the current code:
std::string FSXController::readLine(int offset, FileLookupFlag flag)
{
// Storage Buffer
string line;
streampos sPos(offset);
try
{
// Init stream
if (!m_ifs.is_open())
m_ifs.open("C:\\Users\\guyth\\Documents\\test.txt", fstream::in);
}
catch (int errorCode)
{
showException(errorCode);
return "";
}
// Set stream to read input line
m_ifs.seekg(sPos);
if (!getline(m_ifs, line))
return "";
// Close stream if no multiple selection required
if (flag == FileLookupFlag::single)
m_ifs.close();
return line;
}
This code is in 'bug fix mode' and so therefore is pretty messy, don't worry too much about that, cleanup will happen when this method is finally working.
I have tried:
Absolute file path
Saving path into string and then calling the .c_str() method.
Running VS 2015 in Administrator mode
Ensuring file has read/wright access
Ensuring no duplicate file extensions
Yes the file definitely has content! :D
I'm kinda out of ideas now and am really not sure why this file is refusing to load.
The condition: if (!getline(m_ifs, line)) Repeatedly returns true... :(
EDIT: I've just tried checking m_ifs.fail() immediately after the open and it returns true, so we know the fail flag was triggered :/
Thanks
Guy
Enable exceptions before opening the stream:
m_ifs.exceptions ( std::ifstream::failbit | std::ifstream::badbit );
Otherwise m_ifs.open won't throw.
And you have to catch std::ifstream::failure:
try {
m_ifs.open("C:\\Users\\guyth\\Documents\\test.txt", fstream::in);
}
catch (std::ifstream::failure e) {
std::cerr << "Exception opening file: " << std::strerror(errno) << "\n";
}
See ios::exceptions for more details.
I'm trying to read in a file that should contain only numbers in it. I can successfully read in the entire file if it meets that criteria, but if it so happened to have a letter in it, I need to return false with an error statement.
The problem is I'm finding it hard for my program to error when it finds this character. It can find it no problem, but when it does, it decides to just skip over it.
My code to read in the file and attempt to read in only numbers:
bool compute::Read (ifstream& stream)
{
double value;
string line;
int lineNumber = 1;
if (stream)
{
while (getline(stream, line))
{
lineNumber++;
istringstream strStream(line);
while (strStream >> value)
{
cout << value << endl;
}
}
}
return true;
}
The input file which I use for this is
70.5 61.2 A8 10.2
2
Notice that there is a non-number character in my input file. It should fail and return false at that point.
Currently, all it does is once it hits the "A", it simply returns to the next line, continuing the getline while loop.
Any help with this would be much appreciated.
The stringstream does catch those errors, but you're doing nothing to stop the enclosing loop from continuing when an error is found. You need to tailor your main loop so that it stops when the stringstream finds an error, which you can't do if the stringstream is being reconstructed on each iteration. You should create a for() loop instead and construct the stringstream in the declaration part. And the condition to the loop should be "as long as the stringstream and stream do not catch an error". For example:
for (std::istringstream iss; iss && std::getline(stream, line);)
{
iss.clear();
iss.str(line);
while (iss >> value)
{
std::cout << value << '\n';
}
}
Futhermore, it doesn't look like you need to use std::getline() or std::istringstream if you just want to print each value. Just do:
while (stream >> value) {
std::cout << value << '\n';
}
The above will stop when it finds an invalid character for a double.
You need the code to stop streaming but return false if it hasn't yet reached the end of the "input".
One way, possibly not the most efficient but still one way, to do that is parse a word at a time.
If you read first into a std::string and if it works (so the string is not empty) create an istringstream from that string, or reuse an existing one, and try streaming that into a double value.
If that fails, you have an invalid character.
Of course you can read a line at a time from the file, then split that into words, so that you can output a meaningful error message showing what line the bad text was found.
The issue of reading straight into doubles is that the stream will fail when it reaches end of file.
However it is possible to workaround that too because the reason for failing has an error status which you can check, i.e. you can check if it eofbit is set. Although the f in eofbit stands for "file" it applies to any stream not just files.
Although this method may sound better than reading words into a string first, I prefer that method in normal circumstances because you want to be able to report the error so you'll want to print in the error what was read.
I have a function (addShape) to read ints from a file according to the id it gets. It gets the id and the stream is as parameters. for some reason I get thrown with std::ios_base::failure after reading the last line.
while (is >> id)
addShape(id, is, false);
I thought that this is the safest way to read from a file.
This is how I initialize the stream:
fstream is;
int id = 0;
string filename;
char answer = NULL;
// set exceptions
is.exceptions(fstream::failbit | fstream::badbit);
try { is.open(filename); }
catch (ifstream::failure e)
{
clrscr();
cout << "There was an error opening " << filename << endl;
waitForEscape();
is.close();
return;
}
When you're creating the stream, notice that you're turning on exceptions whenever failbit is set:
// set exceptions
is.exceptions(fstream::failbit | fstream::badbit);
This means that any time a stream operation sets failbit, the stream will throw an exception.
Now, look at this code:
while (is >> id)
addShape(id, is, false);
At some point the read is >> id will fail, either because you run out of data or because the data is malformed. When this happens with exceptions turned off, this will fail by setting failbit and having is >> id evaluate to false, stopping the loop. However, with exceptions turned on, when failbit is set in this case, it will throw an exception.
Depending on what you want to do, you can either
Not set exceptions for failbit, which will cause the loop to stop running when an error occurs, or
Set up an explicit exception handler around the while loop.
Hope this helps!
This is my code:
StockAccount::StockAccount() {
vector<string> temp;
string line;
std::ifstream stockfile("Results.txt");
if (stockfile.is_open()) {
while (stockfile.good()) {
getline(stockfile, line);
istringstream ss(line);
string token;
while (std::getline(ss, token, ',')) {
temp.push_back(token);
}
addStock(temp.at(0), temp.at(1), temp.at(2));
temp.clear();
}
stockfile.close();
} else {
cout << "Unable to open file" << std::endl << std::endl;
}
}
I know it isn't THAT efficient, that is what I am trying to fix. What it is supposed to be doing is:
Read that file line by line.
Parse each line and split it by comma.
Take those 3 values and use it in a method.
I am using that vector temp to store the values, add them to the function and then clear it so that it can be empty and used again to store the next ones ...etc.
I tried printing out each value BEFORE the temp.clear() and they all print out and THEN I get the error. So I know that temp.clear() is the problem. Perhaps I am using the wrong method, or there is a much better way.
I want to try and NOT use boost if possible.
This is the error I'm getting:
libc++abi.dylib: terminating with uncaught exception of type std::out_of_range: vector
Results.txt is a file that looks like.
goog,525,0
msft,34,10
and so on.
while (stockfile.good()) is wrong, and leads to your reading one extra, non-existent line.
That's because you're checking for stream validity before attempting to read a new line; if there's no new line to read, it's only after the call to getline that this condition would evaluate to false but by then it's too late and you're off trying to handle this non-existent line.
That non-existent line does not have three tokens on it, but you are performing no error checking on the tokenization, nor are you ever verifying the size of the vector temp.
So, when you come to try to access those three vector elements, which don't exist, an exception is thrown.
Your loop should look like this:
while (getline(stockfile, line)) {
istringstream ss(line);
string token;
// ...
}
Notice how I directly check for success in the loop condition, which will prevent the loop body from being executed if the actual getline fails.
Why does ifstream set the failbit to 1 after reading the last line of the specified file? How do I know if the specified file has been read correctly?
bool read_csv_file(const char* filename, vector<string>& lines, bool adding = false)
{
if( !adding ) lines.clear();
ifstream csvfile;
csvfile.open(filename);
if( csvfile.is_open() )
{
string line;
while( csvfile.good() && getline(csvfile,line) )
{
lines.push_back(line);
cout << "fail: " << csvfile.fail() << endl;
}
cout << "fail: " << csvfile.fail() << endl;
csvfile.close();
return (!csvfile.fail());
}
return false;
}
The fail bit is set after you run off the end of the file. Once that happens, you mustn't attempt to interpret the result of your input operation. That's perfectly fine, though, and getline will not set the fail bit while there's still any data to be read. So the following standard loop extracts all the lines:
for (std::string line; std::getline(csvfile, line); )
{
// process "line"
}
// all done
The only reason failbit could be set after reading the last
line (or any line) is if there were an error in the library, and
I don't really believe it. If failbit is set, it means that
you didn't read anything. In your case, it will never be set
when you're in the loop; if it were set, getline would have
evaluated to false, and you wouldn't have entered the loop.
And of course, the loop terminates precisely because getline
fails (or would fail—normally, you would not test for
good before doing input, and if you do, consider that the
failbit was set, regardless, if the test fails).
The usual pattern for this sort of thing is:
while ( someInput ) {
// ...
}
if ( csvfile.bad() ) {
// Serious error (disk read error, etc.)...
} else if ( ! csvfile.eof() ) {
// Formatting error...
} else {
// Normal end of file...
}
When someInput is std::getline(), however, you will never
fail because of a formatting error, so the else if above will
never be true (and a lot of code treats hard disk errors as if
they were an end of file, and so ignores the if part as well).
Too check for erroneous reads, you must test badbit using stream.bad().
Failbit indicates failure in operation logic, and apparently getline sets it when reaching EOF (confirmed on my machine).