libstdc++: Implement std::emit_on_flush etc.

This adds the manipulators for use with basic_osyncstream. In order to
detect when an arbitrary basic_ostream<C,T> is the base class of a
basic_syncbuf<C,T,A> object, introduce a new intermediate base class
that stores the data members. The new base class stores a pointer and
two bools, which wastes (sizeof(void*) - 2) bytes of padding. It would
be possible to use the two least significant bits of the pointer for the
two bools, at least for targets where alignof(basic_streambuf) > 2, but
that's left as a possible change for a future date.

Also define basic_syncbuf::overflow to override the virtual function in
the base class, so that single characters can be inserted into the
stream buffer. Previously the default basic_streambuf::overflow
implementation was used, which drops the character on the floor.

libstdc++-v3/ChangeLog:

	* include/std/ostream (__syncbuf_base): New class template.
	(emit_on_flush, noemit_on_flush, flush_emit): New manipulators.
	* include/std/syncstream (basic_syncbuf): Derive from
	__syncbuf_base instead of basic_streambuf.
	(basic_syncbuf::operator=): Remove self-assignment check.
	(basic_syncbuf::swap): Remove self-swap check.
	(basic_syncbuf::emit): Do not skip pubsync() call if sequence
	is empty.
	(basic_syncbuf::sync): Remove no-op pubsync on stringbuf.
	(basic_syncbuf::overflow): Define override.
	* testsuite/27_io/basic_syncstream/basic_ops/1.cc: Test
	basic_osyncstream::put(char_type).
	* testsuite/27_io/basic_ostream/emit/1.cc: New test.
This commit is contained in:
Jonathan Wakely 2020-11-11 00:19:40 +00:00
parent bb6226419f
commit ecba8547dd
4 changed files with 211 additions and 82 deletions

View File

@ -776,6 +776,73 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__ret_os << __x;
return __ret_os;
}
#if __cplusplus > 201703L && _GLIBCXX_USE_CXX11_ABI
template<typename _CharT, typename _Traits>
class __syncbuf_base : public basic_streambuf<_CharT, _Traits>
{
public:
static bool*
_S_get(basic_streambuf<_CharT, _Traits>* __buf) noexcept
{
if (auto __p = dynamic_cast<__syncbuf_base*>(__buf))
return &__p->_M_emit_on_sync;
return nullptr;
}
protected:
__syncbuf_base(basic_streambuf<_CharT, _Traits>* __w = nullptr)
: _M_wrapped(__w)
{ }
basic_streambuf<_CharT, _Traits>* _M_wrapped = nullptr;
bool _M_emit_on_sync = false;
bool _M_needs_sync = false;
};
template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
emit_on_flush(basic_ostream<_CharT, _Traits>& __os)
{
if (bool* __flag = __syncbuf_base<_CharT, _Traits>::_S_get(__os.rdbuf()))
*__flag = true;
return __os;
}
template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
noemit_on_flush(basic_ostream<_CharT, _Traits>& __os)
{
if (bool* __flag = __syncbuf_base<_CharT, _Traits>::_S_get(__os.rdbuf()))
*__flag = false;
return __os;
}
template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
flush_emit(basic_ostream<_CharT, _Traits>& __os)
{
struct _Restore
{
~_Restore() { *_M_flag = _M_prev; }
bool _M_prev = false;
bool* _M_flag = &_M_prev;
} __restore;
if (bool* __flag = __syncbuf_base<_CharT, _Traits>::_S_get(__os.rdbuf()))
{
__restore._M_prev = *__flag;
__restore._M_flag = __flag;
*__flag = true;
}
__os.flush();
return __os;
}
#endif // C++20
#endif // C++11
_GLIBCXX_END_NAMESPACE_VERSION

View File

@ -52,7 +52,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _CharT, typename _Traits = char_traits<_CharT>,
typename _Alloc = allocator<_CharT>>
class basic_syncbuf : public basic_streambuf<_CharT, _Traits>
class basic_syncbuf : public __syncbuf_base<_CharT, _Traits>
{
public:
using char_type = _CharT;
@ -69,22 +69,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
explicit
basic_syncbuf(streambuf_type* __obuf)
: basic_syncbuf(__obuf, allocator_type{})
: basic_syncbuf(__obuf, allocator_type{})
{ }
basic_syncbuf(streambuf_type* __obuf, const allocator_type& __alloc)
: _M_wrapped(__obuf)
, _M_impl(__alloc)
, _M_mtx(__obuf)
: __syncbuf_base<_CharT, _Traits>(__obuf)
, _M_impl(__alloc)
, _M_mtx(__obuf)
{ }
basic_syncbuf(basic_syncbuf&& __other)
: _M_wrapped(__other._M_wrapped)
, _M_impl(std::move(__other._M_impl))
, _M_mtx(std::move(__other._M_mtx))
, _M_emit_on_sync(__other._M_emit_on_sync)
, _M_needs_sync(__other._M_needs_sync)
: __syncbuf_base<_CharT, _Traits>(__other._M_wrapped)
, _M_impl(std::move(__other._M_impl))
, _M_mtx(std::move(__other._M_mtx))
{
this->_M_emit_on_sync = __other._M_emit_on_sync;
this->_M_needs_sync = __other._M_needs_sync;
__other._M_wrapped = nullptr;
}
@ -98,82 +98,93 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{ }
}
basic_syncbuf& operator=(basic_syncbuf&& __other)
basic_syncbuf&
operator=(basic_syncbuf&& __other)
{
if (std::__addressof(__other) != this)
{
emit();
emit();
_M_impl = std::move(__other._M_impl);
this->_M_emit_on_sync = __other._M_emit_on_sync;
this->_M_needs_sync = __other._M_needs_sync;
this->_M_wrapped = __other._M_wrapped;
__other._M_wrapped = nullptr;
_M_mtx = std::move(__other._M_mtx);
_M_impl = std::move(__other._M_impl);
_M_wrapped = __other._M_wrapped; __other._M_wrapped = nullptr;
_M_mtx = std::move(__other._M_mtx);
_M_emit_on_sync = __other._M_emit_on_sync;
_M_needs_sync = __other._M_needs_sync;
}
return *this;
}
void
swap(basic_syncbuf& __other)
swap(basic_syncbuf& __other) noexcept
{
if (std::__addressof(__other) != this)
{
std::swap(_M_impl, __other._M_impl);
std::swap(_M_wrapped, __other._M_wrapped);
std::swap(_M_mtx, __other._M_mtx);
std::swap(_M_emit_on_sync, __other._M_emit_on_sync);
std::swap(_M_needs_sync, __other._M_needs_sync);
}
using _ATr = allocator_traits<_Alloc>;
if constexpr (!_ATr::propagate_on_container_swap::value)
__glibcxx_assert(get_allocator() == __other.get_allocator());
std::swap(_M_impl, __other._M_impl);
std::swap(this->_M_emit_on_sync, __other._M_emit_on_sync);
std::swap(this->_M_needs_sync, __other._M_needs_sync);
std::swap(this->_M_wrapped, __other._M_wrapped);
std::swap(_M_mtx, __other._M_mtx);
}
bool
emit()
{
if (!_M_wrapped)
if (!this->_M_wrapped)
return false;
auto __s = _M_impl.view();
if (__s.empty())
return true;
auto __s = std::move(_M_impl).str();
const lock_guard<__mutex> __l(_M_mtx);
if (_M_wrapped->sputn(__s.data(), __s.size()) != __s.size())
return false;
if (_M_needs_sync)
if (auto __size = __s.size())
{
_M_needs_sync = false;
if (_M_wrapped->pubsync() != 0)
return false;
auto __n = this->_M_wrapped->sputn(__s.data(), __size);
if (__n != __size)
{
__s.erase(0, __n);
_M_impl.str(std::move(__s));
return false;
}
}
_M_impl.str("");
if (this->_M_needs_sync)
{
this->_M_needs_sync = false;
if (this->_M_wrapped->pubsync() != 0)
return false;
}
return true;
}
streambuf_type*
get_wrapped() const noexcept
{ return _M_wrapped; }
{ return this->_M_wrapped; }
allocator_type get_allocator() const noexcept
allocator_type
get_allocator() const noexcept
{ return _M_impl.get_allocator(); }
void
set_emit_on_sync(bool __b) noexcept
{ _M_emit_on_sync = __b; }
{ this->_M_emit_on_sync = __b; }
protected:
int
sync() override
{
auto __res = _M_impl.pubsync();
if (__res == 0)
{
_M_needs_sync = true;
if (_M_emit_on_sync)
return emit() ? 0 : -1;
}
return __res;
this->_M_needs_sync = true;
if (this->_M_emit_on_sync && !emit())
return -1;
return 0;
}
int_type
overflow(int_type __c) override
{
int_type __eof = traits_type::eof();
if (__builtin_expect(!traits_type::eq_int_type(__c, __eof), true))
return _M_impl.sputc(__c);
return __eof;
}
streamsize
@ -181,11 +192,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{ return _M_impl.sputn(__s, __n); }
private:
streambuf_type* _M_wrapped;
using __impl_type = basic_stringbuf<char_type, traits_type,
allocator_type>;
__impl_type _M_impl;
basic_stringbuf<char_type, traits_type, allocator_type> _M_impl;
struct __mutex
{
@ -203,15 +210,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
void
lock()
{
if (_M_mtx)
_M_mtx->lock();
_M_mtx->lock();
}
void
unlock()
{
if (_M_mtx)
_M_mtx->unlock();
_M_mtx->unlock();
}
// FIXME: This should be put in the .so
@ -225,31 +230,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
return __m[__key];
}
#else
__mutex(void*)
{ }
void
swap(__mutex&&) noexcept
{ }
void
lock()
{ }
void
unlock()
{ }
__mutex(void*) { }
void swap(__mutex&&) noexcept { }
void lock() { }
void unlock() { }
#endif
__mutex(const __mutex&) = delete;
__mutex& operator=(const __mutex&) = delete;
__mutex(__mutex&&) = default;
__mutex& operator=(__mutex&&) = default;
};
__mutex _M_mtx;
bool _M_emit_on_sync = false;
bool _M_needs_sync = false;
};
template <typename _CharT, typename _Traits = char_traits<_CharT>,

View File

@ -0,0 +1,44 @@
// Copyright (C) 2020 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.
// { dg-options "-std=gnu++2a" }
// { dg-additional-options "-pthread" { target pthread } }
// { dg-do run { target c++2a } }
// { dg-require-effective-target cxx11-abi }
#include <syncstream>
#include <testsuite_hooks.h>
void
test01()
{
std::stringbuf sb;
std::osyncstream s(&sb);
s << "abc" << std::emit_on_flush << "def" << std::flush << "ghi"
<< std::emit_on_flush << std::noemit_on_flush << std::endl;
VERIFY( sb.view() == "abcdef" );
s << "jkl" << std::flush_emit << "mno" << std::flush;
VERIFY( sb.view() == "abcdefghi\njkl" );
s.emit();
VERIFY( sb.view() == "abcdefghi\njklmno" );
}
int
main()
{
test01();
}

View File

@ -123,12 +123,41 @@ test04() // emitting
s.emit();
VERIFY( b.str() == txt );
}
{
std::stringbuf b;
std::osyncstream s(&b);
s.put('a');
s.put('b');
s.put('c');
s.emit();
VERIFY( b.str() == "abc" );
}
{
std::stringbuf b;
std::osyncstream s(&b);
s << "abc";
s.put(' ');
s << "def";
s.emit();
VERIFY( b.str() == "abc def" );
s << "ghi";
s.put(' ');
s << "jkl";
s.emit();
VERIFY( b.str() == "abc defghi jkl" );
}
}
int main()
{
test01();
test02();
test03();
test04();
return 0;
}