Search code examples
c++stringfile-handlingeofgetline

What's actually happens when we try to extract line in file after which `eof` character is present with istream::getline() and std::getline()


roha.txt

I really love to spend time with you.
Let's go for coffee someday.
Enjoy whole day and cherish the memories.

Code-1

#include <iostream>
#include <fstream>


int main()
{
    char str[100];

    std::ifstream fin;
    fin.open("roha.txt", std::ios::in);

   for(int i=0; i<=3; i++)
   {
        std::cout<<bool(fin.getline(str,100) )<<" "<<str<<fin.fail()<<"\n";
   }

}

Output

1 I really love to spend time with you.0
1 Let's go for coffee someday.0
1 Enjoy whole day and cherish the memories.0
0 1

Code-2

#include <iostream>
#include <fstream>
#include <string>
using std::string;

int main()
{
   string str;

    std::ifstream fin;
    fin.open("roha.txt", std::ios::in);

    for(int i=0; i<=3; i++)
    {
        std::cout<<bool(std::getline(fin,str) )<<" "<<str<<fin.fail()<<"\n";
    }

}

Output

1 I really love to spend time with you.0
1 Let's go for coffee someday.0
1 Enjoy whole day and cherish the memories.0
0 Enjoy whole day and cherish the memories.1

I know C-style char array, istream::getline and string, std::getline are different. But I want to know what actually happens.

I am guessing that for string, std::getline, it extracts string for 1st, 2nd and when it tries to extract the 3rd time it sees eof so it extracts just before eof.

The next time we try to extract it, just encounters eof so it didn't extract anything and set fail-bit.

string str didn't get modified, so when we try to print it, only the string last extracted gets printed.

I don't know if what I’m thinking is right...

Also I can’t make any such case regarding istream::getline(), C-style char array.


Solution

  • To quote the standard, section 21.3.3.4 inserters and extractors [string.io]:

    Clause 6:

    […] After constructing a sentry object, if the sentry converts to true, calls str.erase() and then extracts characters from is and appends them to str […] until any of the following occurs:

    • end-of-file occurs on the input sequence (in which case, the getline function calls is.setstate(ios_base::eofbit)).
    • […]

    Section 29.7.4.1.3 Class basic_istream::sentry:

    explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false); Effects: If is.good() is false, calls is.setstate(failbit) […] If, after any preparation is completed, is.good() is true, ok_ != false otherwise, ok_ == false. During preparation, the constructor may call setstate(failbit) […]

    explicit operator bool() const; Returns: ok_

    So, what is happening with the string version:

    1. You extract the last string. This sets eofbit, but not failbit
    2. You getline again
    3. getline constructs a sentry
    4. The sentry checks is.good(). This is false because eofbit is set
    5. The sentry sets the failbit and sets its member ok_ to false
    6. The getline function checks if the sentry is true (operator bool). This is false
    7. The getline function returns before clearing your old string

    Section 29.7.4.3 Unformatted input functions

    Clause 21 (this is about the C-string version):

    In any case, if n is greater than zero, it then stores a null character (using charT()) into the next successive location of the array

    The rest of the wording is similar to the string version. In other words, the C-string version of getline always stores a '\0' character, even if it failed. The std::string version does not, presumably because it doesn't introduce the same memory safety issues that you have with the C version if you forget to check the failbit.