1. IO Stream

5 minute read

Published:

int sum_ints(std::istream& is) {     
    int i, sum = 0;
    while (is >> i) {      // extract a number to int i
        sum += i;
        std::cout << "current sum: " << std::setw(4) << sum;// 4 space, right align
        print_io_states(is);
    }
    return sum;
}

Keeps printing the sum and stats

echo "5 7 9 " | ./io1

istream

while (is >> i);

istream& istream::operator>>(int& value);     // modifies istream (this)
ios::operator bool() const;
// same as !ios::fail
  • Return istream so can keep chaining things
  • operator <type> is a conversion operation
    • Invoked when <type> is required
  • istream is a base class of ios

  • std::setw() is a function that returns an object WidthManipulator
    ostream& operator<<(ostream& os, const WidthManipulator& wm) {
      os.width(wm.get_width());
      return os;
    }
    

State

void print_io_states(std::ios& io) {
    std::cout << '\t';
    std::cout << " fail()==" << io.fail();    // bad + eof
    std::cout << "  bad()==" << io.bad();
    std::cout << "  eof()==" << io.eof();
    std::cout << '\n';
}

Hierarchy

![[09-io-stream-hierarchy.svg]]

  • std::cout and std::cin are instances of ostream and istream
  • ostingstream prints into a string
  • ofstream prints into a file

    iostate

    Top level ios_base contains flags and constants, such as ios_base::iostate

iostate is a set of flags telling the state of the IO, can be combined

  • failbit: formatting/extraction error/EOF
  • badbit: irrecoverable error (E. disk crashed)
  • `eofbit

In class ios, flags can be accessed by:

  • fail() (failbit OR badbit)
    • operator bool() same as !fail()
  • bad()
  • eof()
    echo "10 2 3 4" | ./io1
    

    Only after summing 4, and trying to read for one more time, hits EOF

  • echo adds a \n at the end of “4”. ios hits the \n first and parses the 4 okay

Now suppress \n by echo -n

echo -n "10 2 3 4" | ./io1
  • Now, when summing “4”, eof() set, since hit EOF directly after “4”
    • fail() = 0, eof() = 1
    • is >> i still evaluated to 1, so loop executes one more time
  • Last time, fail() = 1, eof() = 1 (still)

Error Recovery

Keep recovering until eof()

int sum_ints(std::istream& is) {
    int i, sum = 0;
    while (!is.eof()) {
        is >> i;      // skip all leading whitespaces
        
        if (is.bad()) {
            throw std::runtime_error{"bad istream"};
        } 
        
        else if (is.fail()) {    // fail(), but not bad()
            if (is.eof()) {
                // failbit && eofbit: this is normal (trailing whitespace)
                break;
            }
            // failbit && !eofbit: probably non-digit; just skip it
            is.clear();
            char c;
            is.get(c);  // consume a single char
            std::cout << "  skipped: " << c << '\n';
        } 
        
        else {    // read i successfully;
            // we could have hit eof or not (depends on trailing whitespace)
            sum += i;
            std::cout << "current sum: " << std::setw(4) << sum;
            print_io_states(is);
        }
    }
    return sum;
}
  • If get char, consume one character at a time
  • If get int, consume a chunk

C vs Cpp

By default: std::cin is sync with stdin, and for out and err

  • Cpp streams are unbuffered
  • Each IO operation on cpp stream is immediately applied to the corresponding C stream’s buffer
  • Underneath, call the C functions (E. printf(), scanf())
    • Thin wrapper

Can detach Cpp stream from C stream, automatically add its own buffer:

std::ios_base::sync_with_stdio(false);
  • Cpp streams are allowed to buffer its IO independently
  • Lose the following:
    • No longer thread-safe
      • No
    • \n may not flush stdout to terminal
      • Use '\n' instead of std::endl for terminal output

Polymorphic ios

pair<T1, T2> is a library template with two types

void f1(istream& is) {
    while (!is.eof()) {
        is >> std::ws;          // discard leading whitespace
        if (!is || is.eof())    // break if nothing else
            break;
        
        pair<string,double> e;
        is >> e;                // overload >> to read from istream
    
        if (is.fail())
            break;
        cout << e << '\n';
    }

    if (is.bad())
        throw runtime_error{"bad istream"};
    else if (is.fail())      // fail but not just eof
        throw invalid_argument{"bad grade format"};
}

int main(int argc, char **argv) {
    try {
        f1(cin);
    } catch (const exception& x) {
        cerr << x.what() << '\n';
    }
}

Now parse pair from istream:

std::istream& getline(std::istream& is, std::string& str, char delim);

static istream& operator>>(istream& is, pair<string,double>& e) {
    char c;
    // 1. read opening quote, skipping whitespace
    if (is >> c && c == '"') {
        string student;
        // 2. read all chars into student, stop at closing quote, then discard it
        if (std::getline(is, student, '"')) {
            // 3. read a comma, skipping whitespace
            if (is >> c && c == ',') {
                double grade;
                // 4. read double grade
                if (is >> grade) {
                    e = make_pair(student, grade);
                    return is;
                }
            }
        }
    }
    // if fall here, fail :(
    is.setstate(ios_base::failbit);
    return is;
}

static ostream& operator<<(ostream& os, const pair<string,double>& e) {
    return os << "[" << e.first << "] (" << e.second << ")";
}
  • make_pair()

String Stream

Issue with f1(): one failed record failed will fail break and throw

  • Want recover and move on

f2() adds another layer of buffer

  1. Read an entire line
  2. Construct istringstream object from the line, as backup
  3. Call f1(iss)
    • iss becomes the istream source of f1(), instead of cin
    • If error, print ```cpp void f2(istream& is) { string str;

    while (std::getline(is, str)) { istringstream iss(str);

     try {
         f1(iss);
     } catch (const invalid_argument& x) {
         cerr << x.what() << ": " << str << '\n';
     }  } } ```
    

File Stream

f3() constructs ifstream object

void f3(const char* filename) {
    ifstream ifs { filename };
    if (!ifs) {
        if (filename != nullptr)
            throw runtime_error{"can't open file: "s + filename};
        else
            throw runtime_error{"no file name provided"};
    }
    f2(ifs);
}