1
void graph::fillTable()
{
  ifstream fin;
  char X;
  int slot=0;

  fin.open("data.txt");

  while(fin.good()){

  fin>>Gtable[slot].Name;
  fin>>Gtable[slot].Out;
  cout<<Gtable[slot].Name<<endl;
  for(int i=0; i<=Gtable[slot].Out-1;i++)
    {
      **//cant get here**
    fin>>X;
    cout<<X<<endl;
    Gtable[slot].AdjacentOnes.addFront(X);
    }
  slot++;
  }
 fin.close();
}

That's my code, basically it does exactly what I want it to but it keeps reading when the file is not good anymore. It'll input and output all the things I'm looking for, and then when the file is at an end, fin.good() apparently isn't returning false. Here is the text file.

A 2 B F

B 2 C G

C 1 H

H 2 G I

I 3 A G E

F 2 I E

and here is the output

A
B
F
B
C
G
C
H
H
G
I
I
A
G
E
F
I
E

Segmentation fault

-

Here's is Gtable's type.

struct Gvertex:public slist
  {
    char Name;
    int VisitNum;
    int Out;
    slist AdjacentOnes;
    //linked list from slist
  };

I'm expecting it to stop after outputting 'E' which is the last char in the file. The program never gets into the for loop again after reading the last char. I can't figure out why the while isn't breaking.

Tyler Pfaff
  • 4,900
  • 9
  • 47
  • 62
  • I have also tried using eof() to no avail. – Tyler Pfaff Nov 23 '11 at 11:58
  • 1
    why do you think it segfaults when reading? what is Gtable? isnt it more likely that you access a nonexisting index? what did the debugger tell you about the exact place and instruction leading to the segfault? – PlasmaHH Nov 23 '11 at 12:10
  • It may segfault from something else, but I don't want to do any operations once the file is finished regardless. I'm pretty sure it segfaults while trying to read an empty part of the file. Gtable is an array of objects. This array is initialized to 100 slots for no reason other than to make sure that I'm not going out of index! – Tyler Pfaff Nov 23 '11 at 12:15
  • @TylerPaff: well, did you know that any kind of `eof()` or `good()` only gets set *after* a read failed, and that you usually should check whether a read has succeeded, and *after* that ask for the reason via `eof()` ? – PlasmaHH Nov 23 '11 at 12:22
  • 1
    You **do** know the standard C++ library provides automatically resizing containers, yeah? Why are you mucking around with clumsy and error-prone arrays? Also, could we see the definition of whatever it is that Gtable is an array of? – Karl Knechtel Nov 23 '11 at 12:23
  • @Karl Knetchel you may want to look at the tags, I would love to be using a resizable container! – Tyler Pfaff Nov 23 '11 at 12:28
  • Oh, how unfortunate, your instructor is one of the majority who believes reinventing wheels poorly and without guidance has educational value. :( – Karl Knechtel Nov 23 '11 at 12:41
  • :( all of my instructors teach that way actually. You should've seen the amazing vector class I made last year *sarcasm* – Tyler Pfaff Nov 23 '11 at 12:45
  • @PlasmaHH It's worse than that. Whether `eof()` or `good()` are set before or after the last read fails is not specified. If `eof()` is true, the **next** read will fail, but it doesn't tell you whether the preceding read succeeded or failed. And if `eof()` is false, the preceding read succeeded, but the next read can succeed or fail. `good()` suffers from the same problem, because it includes the `eofbit`. – James Kanze Nov 23 '11 at 13:52

5 Answers5

5

Your condition in the while loop is wrong. ios::eof() isn't predictive; it will only be set once the stream has attempted (internally) to read beyond end of file. You have to check after each
input.

The classical way of handling your case would be to define a >> function for GTable, along the lines of:

std::istream&
operator>>( std::istream& source, GTable& dest )
{
    std::string line;
    while ( std::getline( source, line ) && line.empty() ) {
    }
    if ( source ) {
        std::istringstream tmp( line );
        std::string name;
        int count;
        if ( !(tmp >> name >> count) ) {
            source.setstate( std::ios::failbit );
        } else {
            std::vector< char > adjactentOnes;
            char ch;
            while ( tmp >> ch ) {
                adjactentOnes.push_back( ch );
            }
            if ( !tmp.eof() || adjactentOnes.size() != count ) {
                source.setstate( std::ios::failbit );
            } else {
                dest.Name = name;
                dest.Out = count;
                for ( int i = 0; i < count; ++ i ) {
                    dest.AdjacentOnes.addFront( adjactentOnes[ i ] );
                }
            }
        }
    }
    return source;
}

(This was written rather hastily. In real code, I'd almost certainly factor the inner loop out into a separate function.)

Note that:

  • We read line by line, in order to verify the format (and to allow resynchronization in case of error).

  • We set failbit in the source stream in case of an input error.

  • We skip empty lines (since your input apparently contains them).

  • We do not modify the target element until we are sure that the input is correct.

One we have this, it is easy to loop over all of the elements:

int slot = 0;
while ( slot < GTable.size() && fin >> GTable[ slot ] ) {
    ++ slot;
}
if ( slot != GTable.size )
    //  ... error ...

EDIT:

I'll point this out explicitly, because the other people responding seem to have missed it: it is absolutely imperative to ensure that you have the place to read into before attempting the read.

EDIT 2:

Given the number of wrong answers this question is receiving, I would like to stress:

  • Any use of fin.eof() before the input is known to fail is wrong.

  • Any use of fin.good(), period, is wrong.

  • Any use of one of the values read before having tested that the input has succeeded is wrong. (This doesn't prevent things like fin >> a >> b, as long as neither a or b are used before the success is tested.)

  • Any attempt to read into Gtable[slot] without ensuring that slot is in bounds is wrong.

With regards to eof() and good():

The base class of istream and ostream defines three “error” bits: failbit, badbit and eofbit. It's important to understand when these are set: badbit is set in case of a non-recoverable hardward error (practically never, in fact, since most implementations can't or don't detect such errors); and failbit is set in any other case the input fails—either no data available (end of file), or a format error ("abc" when inputting an int, etc.). eofbit is set anytime the streambuf returns EOF, whether this causes the input to fail or not! Thus, if you read an int, and the stream contains "123", without trailing white space or newline, eofbit will be set (since the stream must read ahead to know where the int ends); if the stream contains "123\n", eofbit will not be set. In both cases, however, the input succeeds, and failbit will not be set.

To read these bits, there are the following functions (as code, since I don't know how to get a table otherwise):

eof():   returns eofbit
bad():   returns badbit
fail():  returns failbit || badbit
good():  returns !failbit && !badbit && !eofbit

operator!():      returns fail()
operator void*(): returns fail() ? NULL : this
    (typically---all that's guaranteed is that !fail() returns non-null.)

Given this: the first check must always be fail() or one of the operator (which are based on fail). Once fail() returns true, we can use the other functions to determine why:

if ( fin.bad() ) {
    //  Serious problem, disk read error or such.
} else if ( fin.eof() ) {
    //  End of file: there was no data there to read.
} else {
    //  Formatting error: something like "abc" for an int
}

Practically speaking, any other use is an error (and any use of good() is an error—don't ask me why the function is there).

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • This is certainly the best answer here, but you have to realise that this is a homework question. It's likely that the OP is new to programming and doesn't know about/isn't allowed to use stringstreams, standard template library containers or overloaded operators. – Chris Parton Nov 23 '11 at 12:51
  • Pretty much. I don't know of stringstreams or stl. Thank you for the answer but I'm afraid I can't use it. – Tyler Pfaff Nov 23 '11 at 13:05
  • Homework or not, the only acceptable solution in C++ is to write a `>>` operator for the type being read, so that 1) the input is atomic (you either read the data or you don't), and 2) you can use the standard input loop idiom for input. And `getline`, then `istringstream` should be among the first things you learn about iostream, since it is **the** standard idiom for simple parsing. It's not that used in production code, but is (or should be) almost universal for homework type problems (or other quicky code). – James Kanze Nov 23 '11 at 13:48
  • Awesome edit thanks. Can I ask you why good() is there though? =p – Tyler Pfaff Nov 23 '11 at 21:29
  • @TylerPfaff In the standard, for historical reasons. Why it was created to begin with (in the first implementation), I don't know. More generally, I find that the error reporting and handling in iostream is the one thing that doesn't seem well thought out and designed. (But then, the same thing can be said for `FILE*` in C.) – James Kanze Nov 24 '11 at 10:07
3

Slightly slower but cleaner approach:

void graph::fillTable()
{
  ifstream fin("data.txt");
  char X;
  int slot=0;

  std::string line;

  while(std::getline(fin, line))
  {
    if (line.empty()) // skip empty lines
      continue;

    std::istringstream sin(line);
    if (sin >> Gtable[slot].Name >> Gtable[slot].Out && Gtable[slot].Out > 0)
    {
      std::cout << Gtable[slot].Name << std::endl;
      for(int i = 0; i < Gtable[slot].Out; ++i)
      {
        if (sin >> X)
        {
          std::cout << X << std::endl;
          Gtable[slot].AdjacentOnes.addFront(X);
        }
      }
      slot++;
    }
  }
}

If you still have issues, it's not with file reading...

Nim
  • 33,299
  • 2
  • 62
  • 101
  • graph.cpp:25: error: variable âstd::istringstream sinâ has initializer but incomplete type – Tyler Pfaff Nov 23 '11 at 12:35
  • 1
    you need to add `` header – Nim Nov 23 '11 at 12:37
  • Thank you, I have never used it. – Tyler Pfaff Nov 23 '11 at 12:38
  • @Nim But you're still making one of the original mistakes. You're inputting to `Gtable[slot]` before checking that `slot` is in bounds. I'm afraid this is a fatal error---you can't count on the input failing before (and even if the input has exactly the same number of entries as your table, you're still indexing into `Gtable` with an invalid index. That might not be why his code is failing, but if `Gtable` is doing bounds checking, it would be. – James Kanze Nov 23 '11 at 14:03
  • @JamesKanze, for sure - the main aim of this is to fix the way the streams are used, any issue with `Gtable` is entirely up to the OP to solve (it's his homework after all!) - from what I gather in one of the other comments through, he sizes this at something like 100, which should be more than enough... – Nim Nov 23 '11 at 15:26
  • @Nim Perhaps, but I think you'd agree that you should never count on external data read from a file to enforce code invariants:-). – James Kanze Nov 23 '11 at 15:52
  • @JamesKanze, yes - indeed, very foolish... ;) – Nim Nov 23 '11 at 15:57
2

The file won't fail until you actually read from past the end of file. This won't occur until the fin>>Gtable[slot].Name; line. Since your check is before this, good can still return true.

One solution would be to add additional checks for failure and break out of the loop if so.

fin>>Gtable[slot].Name;
fin>>Gtable[slot].Out;
if(!fin) break;

This still does not handle formatting errors in the input file very nicely; for that you should be reading line by line as mentioned in some of the other answers.

zennehoy
  • 6,405
  • 28
  • 55
  • 1
    -1 because the answer is wrong. `ios::good()` is totally worthless as a function, because it may return `false` even after a successful input. – James Kanze Nov 23 '11 at 12:25
  • 1
    Honestly I've never used `ios::good`, since I always test the stream directly (i.e. `if(fin) {`), but from the docs I don't see how it could return false after a successful input. As far as I can tell, `ios::good` returns true if non of `eofbit`, `failbit` and `badbit` are set, which is pretty much the behavior that is to be expected. – zennehoy Nov 23 '11 at 12:37
  • @zennehoy: A successful read of many types (e.g. `int`) can set `eofbit` even during a successful read. – CB Bailey Nov 23 '11 at 13:35
  • @zennehoy You're doing it correctly and idiomatically. The first test should always be for `ios::fail()` (which is what using the stream as a boolean does). Once `ios::fail()` returns true, `ios::eof()` and `ios::bad()` can be used to determine why (but in a lot of school projects, you can just suppose that failure is due to end of file, and be done with it). Any use of `ios::eof()` before having checked for failure is an error. Any use of `ios::good()`, perios, is an error. – James Kanze Nov 23 '11 at 13:55
  • @zennehoy `eofbit` is set if a call to `streambuf::sgetc` returns `EOF`. This may happen because of read-ahead: try reading a single int from `"123"` (no trailing white space or newline) and you'll see what I mean. Use of the stream as a boolean is based on `ios::fail()`, which only considers `failbit` and `badbit`, not `eofbit`. – James Kanze Nov 23 '11 at 13:58
  • @JamesKanze Ah, thanks, learned something new! I never looked closely at what was happening behind `if(stream)`, interesting that it doesn't check for `eofbit`, but makes perfect sense for input like `"123"`. – zennehoy Nov 23 '11 at 14:52
  • @zennehoy `eofbit` is a bit of an awkward situation. Originally, I think it was meant to be mainly internal; in some implementations, if `filebuf::overflow` returned EOF once, it was not guaranteed to do so a second time, so `istream` memorized that it had seen EOF, and didn't go back to it. (But then why is `eofbit` in `good()`?) Practically, I'd like to see a few more error bits: `formatbit` (for errors in format), and an `eofbit` which is only set if the input fails. IMHO, this would facilitate checking as to why an input failed. But we don't have them. – James Kanze Nov 23 '11 at 15:10
1

Try moving first two reads in the while condition:

// assuming Gtable has at least size of 1

while( fin>>Gtable[slot].Name && fin>>Gtable[slot].Out ) {
    cout<<Gtable[slot].Name<<endl;
    for(int i=0; i<=Gtable[slot].Out-1;i++) {
        fin>>X;
        cout<<X<<endl;
        Gtable[slot].AdjacentOnes.addFront(X);
    }
  slot++;

  //EDIT:

  if (slot == table_size) break;
}

Edit: As per James Kanze's comment, you're taking an adress past the end of Gtable array, which is what causes segfault. You could pass the size of Gtable as argument to your fillTable() function (f.ex. void fillTable(int table_size)) and check slot is in bounds before each read.

jrok
  • 54,456
  • 9
  • 109
  • 141
  • @Tyler I'm guessing you're accesing GTable out of bounds. Are there enough "slots" in Gtable? It could help if you show us how you declare Gtable, perhaps. – jrok Nov 23 '11 at 12:25
  • You're still taking the address of `GTable[slot]` before the read fails. The entire logic of his code is wrong: if `GTable` has a fixed size, you **must** check this before trying to read. (A more traditional approach would be for `GTable` to be an `std::vector`, read into a temporary, and `push_back` if the read succeeds. – James Kanze Nov 23 '11 at 12:28
  • @James I agree with and am aware of that. That's why I said it'd be good to see what exactly Gtable is. – jrok Nov 23 '11 at 12:32
  • @jrok added it for you to see. – Tyler Pfaff Nov 23 '11 at 12:41
  • @jrok The array is initialized to 100 elements in the constructor. I don't believe I'm going beyond that. – Tyler Pfaff Nov 23 '11 at 12:57
0

*Edited in response to James' comment - the code now uses a good() check instead of a !eof() check, which will allow it to catch most errors. I also threw in an is_open() check to ensure the stream is associated with the file.*

Generally, you should try to structure your file reading in a loop as follows:

ifstream fin("file.txt");
char a = '\0';
int b = 0;
char c = '\0';

if (!fin.is_open())
    return 1; // Failed to open file.

// Do an initial read. You have to attempt at least one read before you can
// reliably check for EOF.
fin >> a;

// Read until EOF
while (fin.good())
{
    // Read the integer
    fin >> b;

    // Read the remaining characters (I'm just storing them in c in this example)
    for (int i = 0; i < b; i++)
        fin >> c;

    // Begin to read the next line. Note that this will be the point at which
    // fin will reach EOF. Since it is the last statement in the loop, the
    // file stream check is done straight after and the loop is exited.
    // Also note that if the file is empty, the loop will never be entered.
    fin >> a;
}

fin.close();

This solution is desirable (in my opinion) because it does not rely on adding random breaks inside the loop, and the loop condition is a simple good() check. This makes the code easier to understand.

Chris Parton
  • 1,052
  • 9
  • 16
  • -1 because the answer is wrong. (You never use `ios::eof()` as a loop control.) – James Kanze Nov 23 '11 at 12:24
  • Could you explain why not? EDIT: Obviously there are ways other than EOF in which file input can fail, but for typical homework exercises, in-depth error handling isn't usually expected. – Chris Parton Nov 23 '11 at 12:27
  • I have altered my code to use a good() check, which does indeed make more sense. I still feel that the structure of the code is more effective than using breaks everywhere. – Chris Parton Nov 23 '11 at 12:45
  • Because when `ios::eof()` goes true isn't well defined. If `ios::eof()` returns true, you are guaranteed that further input will fail, but if it returns false, you aren't guaranteed anything with regards to further input. `ios::good()` includes `!ios::eof()`, so it is no better. (`ios::eof()` can be used **after** failure, to know whether the failure was due to end of file or some other reason. `ios::good()` isn't even useful then, since after failure, it will always return false.) – James Kanze Nov 23 '11 at 13:44