Question

I am trying to use boost::iostreams to compress data.

The doc for copy() says that its two arguments are closed at the end by calling the template function close() on both of them. My test code is:

#include <iostream>
#include <fstream>

#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/gzip.hpp>

using namespace std;

int main(void)
{
    ifstream ifs("output", ios::binary);
    ofstream ofs("output.boost.gz", ios::binary);

    boost::iostreams::filtering_streambuf<boost::iostreams::output> out;

    out.push(boost::iostreams::gzip_compressor());
    out.push(ofs);

    cout << (ifs.is_open() ? "ifs is opened" : "ifs not opened") << endl;
    cout << (ofs.is_open() ? "ofs is opened" : "ofs not opened") << endl;

    boost::iostreams::copy(ifs, out);

    cout << (ifs.is_open() ? "ifs is opened" : "ifs not opened") << endl;
    cout << (ofs.is_open() ? "ofs is opened" : "ofs not opened") << endl;

    return 0;
}

This test outputs:

ifs is opened
ofs is opened
ifs not opened
ofs is opened

You can see that ofs is still opened. My question is: why? What does boost::iostreams::close() do when passed a filtering_streambuf object?

Was it helpful?

Solution

Interesting.

Going down the rabbit hole[1] it turns out that close_impl<any_tag> is finally reached for the ofstream wrapped deep inside the chain_buf inside the filtering_streambuf. The implementation reads:

template<>
struct close_impl<any_tag> {
    template<typename T>
    static void close(T& t, BOOST_IOS::openmode which)
    {
        if (which == BOOST_IOS::out)
            iostreams::flush(t);
    }

    template<typename T, typename Sink>
    static void close(T& t, Sink& snk, BOOST_IOS::openmode which)
    {
        if (which == BOOST_IOS::out) {
            non_blocking_adapter<Sink> nb(snk);
            iostreams::flush(t, nb);
        }
    }
};

So, as you can see, the documented behaviour is actually just that the linked output stream buffer(s) are flushed (there's also a synch on the containing entity prior to that call, IIRC).

I completely agree that this could have been made a whole lot more explicit.

Reading the TMP code that decides on the specialization:

template<typename T>
struct close_tag {
    typedef typename category_of<T>::type             category;
    typedef typename detail::unwrapped_type<T>::type  unwrapped;
    typedef typename
            iostreams::select<
                mpl::not_< is_convertible<category, closable_tag> >,
                any_tag,
                mpl::or_<
                    is_boost_stream<unwrapped>,
                    is_boost_stream_buffer<unwrapped>
                >,
                close_boost_stream,
                mpl::or_<
                    is_filtering_stream<unwrapped>,
                    is_filtering_streambuf<unwrapped>
                >,
                close_filtering_stream,
                mpl::or_<
                    is_convertible<category, two_sequence>,
                    is_convertible<category, dual_use>
                >,
                two_sequence,
                else_,
                closable_tag
            >::type type;
};

Several workarounds come to mind:

  1. define a specialization of close_tag<> for std::ofstream that actually returns a different tag and make it so that it gets closed (I recommend against this since it can have unintended effects by going against the assumptions held by the devs of Boost Iostreams)

  2. use a boost class for the output stream:

#include <iostream>
#include <fstream>

#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/filter/gzip.hpp>

using namespace std;

int main(void)
{
    cout << boolalpha;

    ifstream ifs("output", ios::binary);
    boost::iostreams::file_sink ofile("output.boost.gz");

    boost::iostreams::filtering_streambuf<boost::iostreams::output> out;
    out.set_auto_close(true);

    out.push(boost::iostreams::gzip_compressor());
    out.push(ofile);

    cout << "out.is_complete(): " << out.is_complete() << endl;
    cout << "ifs.is_open()? "     << ifs.is_open()     << endl;
    cout << "ofile.is_open()? "   << ofile.is_open()   << endl;

    boost::iostreams::copy(ifs, out);

    cout << "out.is_complete(): " << out.is_complete() << endl;
    cout << "ifs.is_open()? "     << ifs.is_open()     << endl;
    cout << "ofile.is_open()? "   << ofile.is_open()   << endl;
}

See it Live on Coliru


[1] It is a surprisingly large rabbit hole, I must add. I wonder what benefit all this genericity really has

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top