For what reason ostream::write(...)
function swallows all exceptions inside it?
From STL "ostream" header:
...
#define _CATCH_IO_END _CATCH_ALL /* catch block for _Myios */ \
...
_Myt& __CLR_OR_THIS_CALL write(const _Elem *_Str,
streamsize _Count)
{ // insert _Count characters from array _Str
ios_base::iostate _State = ios_base::goodbit;
const sentry _Ok(*this);
if (!_Ok)
_State |= ios_base::badbit;
else if (0 < _Count)
{ // state okay, insert characters
_DEBUG_POINTER(_Str);
_TRY_IO_BEGIN
if (_Myios::rdbuf()->sputn(_Str, _Count) != _Count)
_State |= ios_base::badbit;
_CATCH_IO_END
}
_Myios::setstate(_State);
return (*this);
}
From STL "xstddef" header:
#if _HAS_EXCEPTIONS
#define _TRY_BEGIN try {
#define _CATCH(x) } catch (x) {
#define _CATCH_ALL } catch (...) {
#define _CATCH_END
So, ostream::write(..) function would newer throw any exception.
I wrote custom streambuf, which allows one thread to put characters to it, and another thread to get characters from it - with appropriate blocking. If reader thread closed this streambuf, any further invocation of owerflow(...) would throw an exception. But I don't want ostream::write(...)
to swallow this exception (and don't want to write custom ostream
).
Of course, I can check closed() status every time before writing, but I beleive exceptions is the most elegant way in that case.
Full code:
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <iostream>
#include <streambuf>
#include <string>
#include <stdexcept>
#include <vector>
class pipe_closed_exception : public std::exception
{
};
template<class _Elem, class _Traits = std::char_traits<_Elem>>
class basic_pipebuf:
public std::basic_streambuf<_Elem, _Traits>
{
public:
typedef typename std::vector<_Elem> buffer_type;
typedef typename buffer_type::size_type buffer_size_type;
std::mutex m_mutex;
std::condition_variable m_condition;
bool m_closed;
buffer_type m_buffer;
_Elem* m_begin;
_Elem* m_end;
buffer_size_type m_chunk_size;
public:
basic_pipebuf(buffer_size_type buffer_size = 128, buffer_size_type chunk_size = 16):
m_closed(false),
m_buffer(buffer_size),
m_begin(&m_buffer[0]),
m_end(&m_buffer[0] + m_buffer.size()),
m_chunk_size(chunk_size)
{
setp(m_begin + m_chunk_size, m_begin + 2 * m_chunk_size);
setg(m_begin, m_begin + m_chunk_size, m_begin + m_chunk_size);
}
void close()
{
std::unique_lock<std::mutex> lock(m_mutex);
m_closed = true;
m_condition.notify_all();
}
private:
int_type overflow(int_type c)
{
std::unique_lock<std::mutex> lock(m_mutex);
if (m_closed)
{
throw pipe_closed_exception();
}
if (_Traits::eq_int_type(_Traits::eof(), c))
return _Traits::not_eof(c);
int_type ret = _Traits::eof();
if (epptr() < m_end)
{
while (epptr() == eback() && !m_closed)
m_condition.wait_for(lock, std::chrono::seconds(1));
if (epptr() != eback())
{
setp(pbase() + m_chunk_size, epptr() + m_chunk_size);
ret = *_Pninc() = c;
}
}
else
{
while (!(eback() > m_begin || m_closed))
m_condition.wait_for(lock, std::chrono::seconds(1));
if (eback() > m_begin)
{
setp(m_begin, m_begin + m_chunk_size);
ret = *_Pninc() = c;
}
}
m_condition.notify_one();
return ret;
}
int_type underflow()
{
std::unique_lock<std::mutex> lock(m_mutex);
int_type ret = _Traits::eof();
if (eback() != pbase())
{
if (egptr() < m_end)
{
while (egptr() == pbase() && !m_closed)
m_condition.wait_for(lock, std::chrono::seconds(1));
if (egptr() != pbase())
{
setg(eback() + m_chunk_size, eback() + m_chunk_size, egptr() + m_chunk_size);
}
else // if m_closed
{
setg(pbase(), pbase(), pptr());
}
ret = _Traits::to_int_type(*gptr());
}
else
{
while (!(pbase() > m_begin || m_closed))
m_condition.wait_for(lock, std::chrono::seconds(1));
if (pbase() > m_begin)
{
setg(m_begin, m_begin, m_begin + m_chunk_size);
}
else // if m_closed
{
setg(pbase(), pbase(), pptr());
}
ret = _Traits::to_int_type(*gptr());
}
}
m_condition.notify_one();
return ret;
}
};
template<class _Elem, class _Traits = std::char_traits<_Elem>>
class basic_opipestream :
public std::basic_ostream<_Elem, _Traits>
{
typedef typename basic_pipebuf<_Elem, _Traits> buffer_type;
buffer_type* mPbuf;
public:
basic_opipestream(buffer_type* pBuf) :
std::basic_ostream<_Elem, _Traits>(pBuf),
mPbuf(pBuf)
{
}
void close()
{
mPbuf->close();
}
};
template<class _Elem, class _Traits = std::char_traits<_Elem>>
class basic_ipipestream :
public std::basic_istream<_Elem, _Traits>
{
typedef typename basic_pipebuf<_Elem, _Traits> buffer_type;
buffer_type* mPbuf;
public:
basic_ipipestream(buffer_type* pBuf) :
std::basic_istream<_Elem, _Traits>(pBuf),
mPbuf(pBuf)
{
}
void close()
{
width();
mPbuf->close();
}
};
typedef basic_pipebuf<char> pipebuf;
typedef basic_opipestream<char> opipestream;
typedef basic_ipipestream<char> ipipestream;
typedef basic_pipebuf<wchar_t> wpipebuf;
typedef basic_opipestream<wchar_t> wopipestream;
typedef basic_ipipestream<wchar_t> wipipestream;
static void writer(wopipestream& out)
{
try
{
for (int i = 0; i < 10; ++i)
{
out.write(L"12345678", 8);
out.write(L"qwertyui", 8);
}
out.close();
}
catch (pipe_closed_exception&) // This catch would newer be called!!! (It's really sad)
{
std::wcout << "Pipe closed by reader, stopping write" << std::endl;
}
}
static void reader(wipipestream& in)
{
std::wstring mystr(6, L' ');
in.read(&mystr[0], 6);
std::wcout << "Read 6 symbols from stream: " << mystr << std::endl;
std::wcout << "Closing buffer by reader" << std::endl;
in.close();
}
int main()
{
wpipebuf tb(12, 4);
wopipestream os(&tb);
wipipestream is(&tb);
std::thread write(&::writer, std::ref(os));
std::thread read(&::reader, std::ref(is));
write.join();
read.join();
}