boost::iostreams::copy() закрывает источник, но не приемник

Я пытаюсь использовать boost::iostreams для сжатия данных.

Документ для copy() говорит что два его аргумента закрываются в конце вызовом функции шаблона close() для них обоих. Мой тестовый код:

#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;
}

Этот тест выводит:

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

Вы можете видеть, что офс все еще открыт. Мой вопрос: почему? Что делает boost::iostreams::close() при передаче объекта filtering_streambuf?


person fireboot    schedule 20.12.2013    source источник


Ответы (1)


Интересно.

Спускаясь по кроличьей норе[1], оказывается, что close_impl<any_tag> наконец-то достигнуто для ofstream, заключенного глубоко внутри chain_buf внутри filtering_streambuf. Реализация гласит:

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);
        }
    }
};

Итак, как вы можете видеть, задокументированное поведение фактически заключается в том, что буфер(ы) связанного потока вывода сбрасываются (есть также синхронизация на содержащем объекте перед этим вызовом, IIRC).

Я полностью согласен с тем, что это можно было бы сделать намного более явным.

Чтение кода TMP, который определяет специализацию:

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;
};

На ум приходят несколько обходных путей:

  1. определить специализацию close_tag<> для std::ofstream, которая на самом деле возвращает другой тег, и сделать так, чтобы он закрывался (я не рекомендую этого делать, поскольку это может иметь непреднамеренные последствия, противореча предположениям разработчиков Boost Iostreams)

  2. используйте класс повышения для выходного потока:

#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;
}

Смотрите Прямой эфир на Coliru


[1] Должен добавить, что это удивительно большая кроличья нора. Интересно, какую пользу на самом деле несет вся эта универсальность?

person sehe    schedule 20.12.2013
comment
Отличный ответ, спасибо; Мне трудно понять причину такого поведения, а также понять, какая часть документации документирует его. Считаете ли вы, что стоит попытаться связаться с разработчиками Boost, чтобы получить ответ на этот вопрос? - person fireboot; 21.12.2013
comment
@fireboot Я так думаю. Похоже, так было задумано. Документация относится к документация close, в которой просто говорится о том, что незакрывающееся - person sehe; 21.12.2013