C++ Stream Guards

ISO/IEC JTC1 SC22 WG21 N3678 - 2013-04-30

Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org

Introduction
Solution
    Stream Guard
    Guard Stream
    Expression Locking
    Block Locking
    Recursive Locking
Wording
    27.7.6 Stream Guard [stream.guard]
    27.7.6.1 Class template stream_guard [stream.guard.class]
    27.7.6.1.1 Types [stream.guard.class.types]
    27.7.6.1.2 Constructors and destructors [stream.guard.class.ctor]
    27.7.6.1.3 Member functions [stream.guard.class.memfn]
    27.7.6.2 Function template guard_stream [stream.guard.fnctn]
    27.7.6.3 Streaming function templates [stream.guard.streaming]
Revision History
References

Introduction

At present, stream output operations guarantee that they will not produce race conditions, but do not guarantee that the effect will be sensible. Some form of external synchronization is required. Unfortunately, without a standard mechanism for synchronizing, independently developed software will be unable to synchronize.

N3535 C++ Stream Mutexes proposed a standard mechanism for finding and sharing a mutex on streams. At the Spring 2013 standards meeting, the Concurrency Study Group requested a change away from a full mutex definition to a definition that also enabled buffering. This paper describes such a change.

A draft of this paper was reviewed in the Library Working Group, and found too many open issues on what was reasonably exposed to the 'buffering' part. In retrospect, I think that if buffering is desired it should be explicit. So, this paper is neither as general as a stream mutex nor as sure as an explicit string_stream buffer. Nevertheless, this paper represents a design point that needs documentation and discussion.

Solution

First, we propose a stream_guard, that serves to batch operations on a stream. That batching may be implemented as mutexes, as buffering, or some combination of both.

Stream Guard

A stream guard is declared with a reference to the stream to be locked. Each stream to be locked may have more than one simultaneous associated stream guard object. Two independent threads creating two stream guard objects on the same stream will mutually exclude blocks of operations on the stream.

std::ostringstream stream;
stream_guard<std::ostream> gout(std::cout);

Guard Stream

To avoid requiring programmers to specify the stream_guard type, we provide a template function that returns the properly typed object from a reference to the stream.

auto gout = guard_stream(std::cout);

Expression Locking

Programmers batch operations in a single full expression.

guard_stream(std::cout) << "1" << "2" << "3" << "4" << "5" << std::endl;

Block Locking

Locking across more than one expression is needed, and the stream guard serves that purpose as well.

{
auto gout = guard_stream(std::cout);
gout << "1";
gout << "2";
gout << "3";
gout << "4";
gout << "5";
gout << std::endl;
}

Recursive Locking

Streams may be locked in uncoordinated code, and so recursive locking must work in some fashion. Because the implementation may use buffering or mutexes, the order of output in the recursive case is unspecified except that when a stream_guard destruction happens before another guarded operation on that stream, the operations of the first guard happen before the operations on the second.

{
auto gout = guard_stream(std::cout);
gout << "1";
guard_stream(std::cout) << "2";
gout << "3" << std::endl;
}

The output could be 123 or 213 but not 132 (because of the destructor happens-before order) and not 231 or (because of the output operations sequence) and not 312 or 321 (because of both).

Wording

This wording is relative to N3485.

27.7.6 Stream Guard [stream.guard]

Add a new section.

The header <stream_guard> defines class and function templates that synchronize access to streams.

27.7.6.1 Class template stream_guard [stream.guard.class]

Add a new section.

There are two stream class templates, one for input and one for output.

template <typename Stream>
  class stream_guard;

template <typename charT, typename traits>
  class stream_guard<std::basic_istream<charT, traits>>

template <typename charT, typename traits>
  class stream_guard<std::basic_ostream<charT, traits>>

template <typename charT, typename traits>
  class stream_guard<std::basic_wistream<charT, traits>>

template <typename charT, typename traits>
  class stream_guard<std::basic_wostream<charT, traits>>

27.7.6.1.1 Types [stream.guard.class.types]

Add a new section.

Both class templates provide the following typedefs to the same named types in the constructor parameter types.

fmtflags, locale, iostate, char_type, traits_type, event_callback, off_type, pos_type

The input class template provides the following typedef to the same named type in the constructor parameter type.

int_type

27.7.6.1.2 Constructors and destructors [stream.guard.class.ctor]

Add a new section.

stream_guard(std::basic_istream<charT, traits>& stm);
stream_guard(std::basic_ostream<charT, traits>& stm);
stream_guard(std::basic_wistream<charT, traits>& stm);
stream_guard(std::basic_wostream<charT, traits>& stm);

Effects: Constructs a stream_guard and initiates a sequence of batched stream operations.

Synchronization: May or may not acquire a mutex for arbitrary portions of the lifetime of the object.

~stream_guard();

Effects: Finalizes a sequence of batched operations, ensuring that all operations are effective on the stream.

If construction and destruction of a single guard occur on different threads, the effects are undefined.

For two guards on a given stream:

  • If construction and destruction are on the same thread:

    • If the destruction of the first guard is sequenced before (1.9) the construction of the second guard, the effects on the stream of the first guard are sequenced before the effects on the stream of the second guard.

    • Otherwise, if the destruction of the first guard is sequenced before (1.9) the destruction of the second guard, the effects on the stream of the first guard are sequenced before the effects on the second stream that are sequenced after the first guard's destruction.

    • Otherwise, the effects may or may not be interleaved, but shall preserve the sequence on individual guards.

  • Otherwise, the two guards are constructed and destructed on separate threads:

    • If the destruction of the first guard happens before (1.10) the construction of the second guard, the effects on the stream of the first guard happen before the construction of the second guard.

    • Otherwise, the effects of one guard happen before the effects of the other guard.

Destroys the holder.

Synchronization: May or may not acquire a mutex for arbitrary portions of the lifetime of the object.

27.7.6.1.3 Member functions [stream.guard.class.memfn]

Add a new section.

Both class templates provides the following functions, which have the same effect as the same functions on the constructor argument object.


fmtflags flags() const;
fmtflags flags(fmtflags fmtfl);
fmtflags setf(fmtflags fmtfl);
fmtflags setf(fmtflags fmtfl, fmtflags mask);
void unsetf(fmtflags mask);
std::streamsize precision() const;
std::streamsize precision(std::streamsize prec);
std::streamsize width() const;
std::streamsize width(std::streamsize wide);
locale getloc() const;
long& iword(int index);
void*& pword(int index);
void register_callback(event_callback fn, int index);
explicit operator bool() const;
iostate rdstate() const;
void clear(iostate state = goodbit);
void setstate(iostate state);
bool good() const;
bool eof() const;
bool fail() const;
bool bad() const;
iostate exceptions() const;
void exceptions(iostate except);
std::basic_ostream<char_type,traits_type>* tie() const;
std::basic_ostream<char_type,traits_type>*
  tie(std::basic_ostream<char_type,traits_type>* tiestr);
std::basic_streambuf<char_type,traits_type>* rdbuf() const;
std::basic_streambuf<char_type,traits_type>*
  rdbuf(std::basic_streambuf<char_type,traits_type>* sb);
std::basic_ios<char_type,traits_type>&
  copyfmt(const std::basic_ios<char_type,traits_type>& rhs);
char_type fill() const;
char_type fill(char_type ch);
locale imbue(const locale& loc);
char narrow(char_type c, char dfault) const;
char_type widen(char c) const;

The input class template provides the following functions, which have the same effect as the same functions on the constructor argument object.

std::streamsize gcount() const;
int_type get();
std::basic_istream<charT,traits>& get(charT& c);
std::basic_istream<charT,traits>& get(charT* s, std::streamsize n);
std::basic_istream<charT,traits>&
  get(charT* s, std::streamsize n, charT delim);
std::basic_istream<charT,traits>&
  get(std::basic_streambuf<charT,traits>& sb);
std::basic_istream<charT,traits>&
  get(std::basic_streambuf<charT,traits>& sb, charT delim);
std::basic_istream<charT,traits>& getline(charT* s, std::streamsize n);
std::basic_istream<charT,traits>&
  getline(charT* s, std::streamsize n, charT delim);
std::basic_istream<charT,traits>&
  ignore(std::streamsize n = 1, int_type delim = traits::eof());
int_type peek();
std::basic_istream<charT,traits>& read(charT* s, std::streamsize n);
std::streamsize readsome(charT* s, std::streamsize n);
std::basic_istream<charT,traits>& putback(charT c);
std::basic_istream<charT,traits>& unget();
int sync();
pos_type tellg();
std::basic_istream<charT,traits>& seekg(pos_type p);
std::basic_istream<charT,traits>&
  seekg(off_type o, std::ios_base::seekdir s);

The output class template provides the following functions, which have the same effect as the same functions on the constructor argument object.

std::basic_ostream<charT,traits>& put(charT c);
std::basic_ostream<charT,traits>& write(const charT* s, std::streamsize n);
std::basic_ostream<charT,traits>& flush();
pos_type tellp();
std::basic_ostream<charT,traits>& seekp(pos_type p);
std::basic_ostream<charT,traits>&
  seekp(off_type o, std::ios_base::seekdir s);

27.7.6.2 Function template guard_stream [stream.guard.fnctn]

Add a new section.

There are two stream function templates, one for input and one for output, that construct stream_guard objects.

template <typename charT, typename traits>
  stream_guard<std::basic_istream<charT, traits>>
    guard_stream(std::istream& arg);
template <typename charT, typename traits>
  stream_guard<std::basic_ostream<charT, traits>>
    guard_stream(std::ostream& arg);
template <typename charT, typename traits>
  stream_guard<std::basic_wistream<charT, traits>>
    guard_stream(std::istream& arg);
template <typename charT, typename traits>
  stream_guard<std::basic_wostream<charT, traits>>
    guard_stream(std::ostream& arg);

Effects: Constructs and returns a stream_guard object.

27.7.6.3 Streaming function templates [stream.guard.streaming]

Add a new section.

There are two function templates, that forward streaming operations to the stream.

template <typename charT, typename traits, typename T>
  const stream_guard<std::basic_istream<charT, traits>>&
    operator>>(const stream_guard<std::basic_istream<charT, traits>>& grd,
               const T&& arg);
template <typename charT, typename traits, typename T>
  const stream_guard<std::basic_ostream<charT, traits>>&
    operator<<(const stream_guard<std::basic_ostream<charT, traits>>& grd,
               const T& arg);
template <typename charT, typename traits>
  const stream_guard<std::basic_istream<charT, traits>>&
    operator>>(const stream_guard<std::basic_istream<charT, traits>>& grd,
               std::basic_istream<charT, traits>&
                 (*arg)(std::basic_istream<charT, traits>&));
template <typename charT, typename traits>
  const stream_guard<std::basic_ostream<charT, traits>>&
    operator<<(const stream_guard<std::basic_ostream<charT, traits>>& grd,
               std::basic_ostream<charT, traits>&
                 (*arg)(std::basic_ostream<charT, traits>&));

Effects: Apply the effects of the corresponding operations on the stream_guard constructor argument object.

Revision History

This paper presents and alternate approach to N3535 - 2013-04-16.

N3535 revised N3395 - 2012-09-23 as follows.

N3395 revised N3354 = 12-0044 - 2012-01-14 as follows.

References

[PSL]
The Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition, functions, flockfile, http://pubs.opengroup.org/onlinepubs/009695399/functions/flockfile.html