Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exceptions on iostreams

I have recently learned that one can opt-in into exceptions for iostreams. In order to not have to check manually if a file is open I tried that and ran into this behaviour:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <vector>

void test(std::istream& is, bool exceptions) {
  try {
    if (exceptions)
      is.exceptions(std::istream::failbit);
    std::vector<int> input;
    std::copy(std::istream_iterator<int>{is}, {}, std::back_inserter(input));
    for (auto x : input) {
      std::cout << x << '\n';
    }
  }
  catch (const std::ios_base::failure& f) {
    std::cerr << "Caught error: " << f.what() << '\n';
  }
}

int main() {
  // Emulates file
  std::stringstream ss("1 2 3\n4 5 6\n7 8 9\n");
  test(ss, true);
}

When exceptions are set off this works normally. But when I use exceptions I get one thrown from basic_ios::clear and I can't think of a reason why.

basic_ios::clear is not listed under the function that can set the failbit according to cppreference.

Thanks in advance.

Edit: The answers below already answer why this happens. My additional question now is how to avoid this exception? My second attempt was to replace std::copy by this loop:

for (int n; is >> n;) {
  input.push_back(n);
}

produced the same exception. Or is this behavior even intended?

Note: clang doesn't show this behavior.

like image 286
sv90 Avatar asked Sep 05 '17 13:09

sv90


2 Answers

Jonesinator has given you the reason of the exception, I would like just to stress that the error is there independently from the exception or not. In fact your function is not equivalent, you do not check the stream after the operation in the no-exception branch. In fact, an error silently happen. If you write the two functions in an equivalent way you will get the an equivalent result:

#include <iostream>
#include <iterator>
#include <sstream>
#include <vector>

void test_exception(std::istream& is) {
  try {
    is.exceptions(std::istream::failbit);
    std::vector<int> input;
    std::copy(std::istream_iterator<int>{is}, {}, std::back_inserter(input));
    for (auto x : input) {
      std::cout << x << '\n';
    }
  }
  catch (const std::ios_base::failure& f) {
    std::cout << "Caught error: " << f.what() << '\n';
  }
}

void test_error_code(std::istream& is) {    
    std::vector<int> input;
    std::copy(std::istream_iterator<int>{is}, {}, std::back_inserter(input));
    if (!is.good()) {
      std::cout << "Caught error!" << std::endl;
      return;
    }
    for (auto x : input) {
      std::cout << x << '\n';
    }     
}

int main() {
  // Emulates file
  std::stringstream ss_error_code("1 2 3\n4 5 6\n7 8 9\n");
  test_error_code(ss_error_code);

  std::stringstream ss_exception("1 2 3\n4 5 6\n7 8 9\n");
  test_exception(ss_exception);
}

output:

Caught error!

Caught error: basic_ios::clear

IMHO, this is a great example why exceptions are superior over result code in the vast majority of the scenario and they should be used as default.

like image 21
Alessandro Teruzzi Avatar answered Nov 10 '22 19:11

Alessandro Teruzzi


Using GDB you can see the error happens when the std::istream_iterator is incremented.

#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x00007ffff71d13fa in __GI_abort () at abort.c:89
#2  0x00007ffff7ae80ad in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7ae6066 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7ae60b1 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ae62c9 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00007ffff7b0eea3 in std::__throw_ios_failure(char const*) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#7  0x00007ffff7b4a82d in std::basic_ios<char, std::char_traits<char> >::clear(std::_Ios_Iostate) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#8  0x00007ffff7b4d52f in std::istream::operator>>(int&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#9  0x00005555555556c2 in std::istream_iterator<int, char, std::char_traits<char>, long>::_M_read (this=0x7fffffffe230) at /usr/include/c++/6/bits/stream_iterator.h:121
#10 0x0000555555555ac2 in std::istream_iterator<int, char, std::char_traits<char>, long>::operator++ (this=0x7fffffffe230) at /usr/include/c++/6/bits/stream_iterator.h:95
#11 0x0000555555555a36 in std::__copy_move<false, false, std::input_iterator_tag>::__copy_m<std::istream_iterator<int, char, std::char_traits<char>, long>, std::back_insert_iterator<std::vector<int, std::allocator<int> > > > (__first=..., __last=..., __result=...) at /usr/include/c++/6/bits/stl_algobase.h:293
#12 0x0000555555555965 in std::__copy_move_a<false, std::istream_iterator<int, char, std::char_traits<char>, long>, std::back_insert_iterator<std::vector<int, std::allocator<int> > > > (__first=..., __last=..., __result=...) at /usr/include/c++/6/bits/stl_algobase.h:386
#13 0x00005555555557e2 in std::__copy_move_a2<false, std::istream_iterator<int, char, std::char_traits<char>, long>, std::back_insert_iterator<std::vector<int, std::allocator<int> > > > (__first=..., __last=..., __result=...) at /usr/include/c++/6/bits/stl_algobase.h:424
#14 0x00005555555554c9 in std::copy<std::istream_iterator<int, char, std::char_traits<char>, long>, std::back_insert_iterator<std::vector<int, std::allocator<int> > > > (__first=..., __last=..., __result=...) at /usr/include/c++/6/bits/stl_algobase.h:456
#15 0x00005555555550ed in test (is=..., exceptions=true) at sample.cpp:12
#16 0x000055555555521c in main () at sample.cpp:25

Unrolling the loop, you can find that it is the last call to increment that causes the problem, i.e. calling std::istream_iterator::operator++ when the input stream is empty.

Looking closer at the stack trace, the final increment is trying std::istream::operator>> when the stream is empty. According to cppreference, that will result in the failbit being set since the operation failed to extract an integer from the stream.

like image 104
Jonesinator Avatar answered Nov 10 '22 17:11

Jonesinator