0

I am trying to use an boost::iostreams output filter to add a string to the beginning and the end of whatever I stream out.

My code below works, but only the first time; the second time, the output seems to get lost somewhere, the write method doesn't even seem to get called. I thought at first I'm sending something to the stream that triggers its fail bit, but the stream seems good.

The same problem occurs on mac and linux, with latest boost release (1.48) and svn trunk, with cout and a file sink as device.

Has anyone actually seen this work ? Is that a bug ? Or am I doing something wrong in my code ?

#include <iostream>
#include <sstream>

#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/operations.hpp>

using std::cin;
using std::cout;
using std::endl;
using std::streamsize;
using std::string;
using std::stringstream;

class add_string_output_filter
 : public boost::iostreams::multichar_output_filter
{
    public:

    template<typename Sink>
    streamsize write(Sink& sink, const char* s, streamsize n)
    {
        string out_string = string(s);

        // remove trailing '\0' to prevent line break
        if (out_string[out_string.size()-1] == '\0')
            out_string = out_string.substr(0, out_string.size()-1);

        string pre_string("prepended string - ");
        string app_string(" - appended string");

        stringstream sstrm;
        sstrm << pre_string << out_string << app_string << endl;

        // TODO: char* to string, back to char* ?!?
        return boost::iostreams::write(sink,
                                       sstrm.str().c_str(),
                                       sstrm.str().length());
    }
};

int main()
{
    boost::iostreams::filtering_ostream out;

    out.push(add_string_output_filter());
    out.push(cout);

    // string #01 is printed,
    // string #02 gets lost
    out << "string #01" << endl;
    out << "string #02" << endl;
}
ssc
  • 9,528
  • 10
  • 64
  • 94

1 Answers1

0

Well, there are a lot of things wrong with your code, but I'm not sure why the second string is not being output. When I try the code, your add_string_output_filter::write is only called once (with "string #01\n"); I would expect it to be called each time the buffer is flushed (so a second time after the second std::endl). But even if it were called a second time, I don't think the output is what you expect or want. For prefixing and suffixing lines, you have to actually look at the data, and process a '\n' character correctly. This is much simpler with a simple output filter:

class PrefixAndSuffixLineOutputFilter : public boost::iostreams::output_filter
{
    bool isAtStartOfLine;
public:
    PrefixAndSuffixLineOutputFilter() : isAtStartOfLine( true ) {}

    template <typename Sink>
    bool
    put( Sink& dest, char ch )
    {
        static std::string const prefix( "prepending string - " );
        static std::string const suffix( " - appended string" );

        bool retval = true;
        if ( isAtStartOfLine )
            retval = boost::iostreams::write( dest, prefix.data(), prefix.size() ) == prefix.size();
        if ( retval && ch == '\n' )
            retval = boost::iostreams::write( dest, suffix.data(), suffix.size() ) == suffix.size();
        if ( retval )
            retval = boost::iostreams::put( dest, ch );
        isAtStartOfLine = ch == '\n';
        return retval;
    }
};

In general, the multichar_output_filter is an optimization. If you read the tutorial, at the end, it says “Note that the implementation of write is very similar to what you would get if you put the implementation of shell_comments_output_filter::put inside a for loop iterating from 0 to n.” This is really the key: write the simple output_filter first, and if the profiler shows that the function calls are causing a performance problem, switch to the multichar_output_filter. (And note that you'd need the same state in it, since you'll have to output the prefix and suffix correctly regardless of where the '\n' occurs in multicharacter buffer.)

I might add that the isAtStartOfLine state is very frequence in output filters. (Note that you cannot simply output prefix after every '\n', since that will result in a dangling string "prefix" after the final '\n', and no "prefix" at the start of the first line.) I had a good example of this in my own code, which inserted a timestamp at the beginning of each line, but Jonathan Turkanis didn't include this particular example in his tutorial.

James Kanze
  • 150,581
  • 18
  • 184
  • 329