Re: [ROOT] Using ifstream

From: Christian Holm Christensen (cholm@nbi.dk)
Date: Mon Aug 30 2004 - 01:25:27 MEST


Hi Zaldy, et al,

On Sun, 2004-08-29 at 06:12 -0400, wrote:
> Hi Zaldy
> 
> The error I think is in while loop:
> 
> 
> while(fin.eof() == false) {
> >    fin >> rn >> spl;
> >   }

That is definitely an error.  However, it's not what G++ complains
about. 

What G++ complains about is that `std::ifstream' has only been forward
declared, like 

	namespace std {
		struct ifstream;
	}

And not fully specified.  My guess is, that you're using an older G++
(at least pre-3.3), and I don't have that, so I cannot reproduce your
error, so the below comments refers only to the ISO/IEC standard. 

> > I am using root Version   3.10/01  28 November 2003
> > with gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)

Ah, as I guessed (sorry didn't see this one)

> > Thanks in advance.
> > Zaldy
> >
> >
> > ===========================Compilation error message
> > g++ -O -Wall -fPIC -Wno-deprecated -D_REENTRANT
> > -I/CERN_SOFT/myroot/include/root   -c -o HitInfo.o HitInfo.cc
> > In file included from HitInfo.cc:3:
> > HitInfo.hh:37:39: warning: no newline at end of file
> > HitInfo.cc: In member function `void HitInfo::Write_To_RootFile(char*)':
> > HitInfo.cc:19: aggregate `std::ifstream fin' has incomplete type and
> > cannot be
> >    defined

As you can see, this really has nothing to do with whether you're using
ROOT or not. 

> > make: *** [HitInfo.o] Error 1
> >
> >
> >
> > ============================Sample  code
> > #include <fstream.h>

This header `fstream.h' is not in the C++ standard.  It's an old
obsolete header that is in G++ 3.x for backward compatibility.  You
should use 

	#include <fstream>

instead. 

> > #include <ifstream.h>

This header `ifstream.h' does not exist in the C++ standard.  It was a
implementation specific header in the older G++ compiler, and should not
be used. 

> > .....
> > .....
> >
> > ifstream in;
> > in.open("Filename.dat",ios::in);

You do not need the second argument, unless you're using an older G++ on
Digital Unix.   Also, the two lines can be written as one:

	std::ifstream in("Filename.dat");

That simple. 

> >   Int_t rn, spl;
> >
> >   while(fin.eof() == false) {
> >    fin >> rn >> spl;
> >   }

You do know this is wrong, as I can see from the code in your tar-ball.
For those who do not know this, the above code will read in the last
line of the input file twice (actually, it will not read the physical
line twice, but it will assign the last two values of rn and spl twice).
The reason for this, is that you test `ios_base::eof' _before_ reading
from the file.   One should always test for end-of-file and error
conditions _after_ reading from the file.  So the code should be 
	
 1'	while (true) { 
 2'	  in >> rn >> spl;
 3'	  if (in.eof()) break; 
 4'	  if (in.fail())
 5'	    throw std::runtime_error("Error while reading from file");
 6'	}

Why this is so, is a bit tricky at first.   The old code was 

 1	while (!in.eof()) 
 2	  in >> rn >> spl;

The first time the program executes line 1, it is obviously false, as
the `ifstream::open' merely checks if the file exists, and if so opens
it.  It has not read anything at that point, not even the EOF mark if
the file is empty.  

The first time it executes line 2, it tries to read in two integers,
skipping over whitespace.  After execution of line 2, the cursor stands
immediately after the last character of the last read integer.   It has
not read the next character, even if it's a new line, EOF mark, or
similar.  

So on subsequent executions of line 1, the curser will stand just after
the last character of the last integer, and has no idea what comes after
that character.   Hence, when the program reads the final numbers in the
file in line 2, it will not know that the next character is the EOF
mark, and the test in line 1 will evaluate to true.  Therefor, the
program merely executes line 2 once again.  However, this time, when it
tries to read in the first integer, it will encounter the EOF mark, and
will do nothing to the variables subsequently, and execution of line 2
is stopped, while the variables rn and spl retains  their old values.
When line 1 is executed next, the test evaluates to false, and the loop
is stopped.   However, the body of the while loop was executed one too
many times with the old values of rn and spl.  

To prevent this, one should _always_ check for EOF _immediately_ after
reading in data, and take appropriate action if EOF is seen (for
example, break out of a loop before pushing back some values in a
container or similar). 

The C++ I/O library is very powerful, but one has to be careful to use
it properly (like most of the C++ Standard Library :-)

Oh, and the failure in line 4' and the subsequent exception thrown in
line 5' is an excellent example of possible misuse of exceptions, unless
we surround code with some thing like 

	std::ifstream in("FileName.dat");
	std::map<int,int> data;
	try {
	  if (!in) 
	    throw std::runtime_error("Couldn't open file");
	  int rn, spl; 
	  while (true) { 
 	    in >> rn >> spl;
 	    if (in.eof()) break; 
 	    if (in.fail())
 	      throw std::runtime_error("Error while reading from file");
	    data[rn] = spl;
 	  }
	}
	catch (std::exception& e) {
	  in.close();
	  throw;
        }

making sure that the program is in a good state after an error.  Of
course, the istream& operator>> and map::operator[](key_type), may throw
too, so we have to be careful how we deal with the exceptions (whether
it's re-thrown or not). 

Yours,

-- 
 ___  |  Christian Holm Christensen 
  |_| |  -------------------------------------------------------------
    | |  Address: Sankt Hansgade 23, 1. th.  Phone:  (+45) 35 35 96 91
     _|           DK-2200 Copenhagen N       Cell:   (+45) 24 61 85 91
    _|            Denmark                    Office: (+45) 353  25 404
 ____|   Email:   cholm@nbi.dk               Web:    www.nbi.dk/~cholm
 | |



This archive was generated by hypermail 2b29 : Sun Jan 02 2005 - 05:50:09 MET