Search code examples
c++c++11vectorboost-iostreams

How to create a member vector of filtering streams?


Let's start with a simple compressed file reader class using boost::iostreams:

class SingleFileOpener{
    public:
        SingleFileOpener(const std::string& filename, bool is_compressed) {
            if(is_compressed) m_stream.push(bio::zlib_decompressor());
            m_stream.push(bio::file_source{filename});
        }

        void print() {
            bio::copy(m_stream, std::cout);
        }
    private:
        using unseekable_stream = boost::iostreams::filtering_istream;
        unseekable_stream m_stream;
};

Now calling SingleFileOpener("input.txt", true) followed by print() works correctly. Coliru Link

I want to extend my class to read and manipulate multiple files in a similar manner. Below is the sample code I tried out(commented out in the Coliru link above too):

class MultiFileOpener{
    public:
        MultiFileOpener(const std::vector<std::string> filenames, std::vector<bool> is_compressed) {
            for(auto i = 0u; i < filenames.size(); i++) {
                unseekable_stream s;
                if(is_compressed[i]) s.push(bio::zlib_decompressor());
                s.push(bio::file_source{filenames[i]});
                m_stream.emplace_back(s); // <- error: use of deleted function(copy ctor)
            }
        }

        void print(int i) {
            bio::copy(*m_stream[i], std::cout);
        }
    private:
        using unseekable_stream = boost::iostreams::filtering_istream;
        std::vector<boost::optional<unseekable_stream>> m_stream;
};

The above doesnt compile due to missing copy constructors in base classes. I've tried using boost::optional, std::shared_ptr and miscellaneous alternatives used for delayed initialization. Uptil now the only solution that has worked is to use an initializer list constructor for the std::vector, i.e. doing ctor: m_stream(filenames.size()) {...}. I had 2 questions:

  1. Why is a copy constructor even being called here?
  2. Is it possible to do this without the initializer list way?

Solution

  • Why is a copy constructor even being called here?

    Here:

    m_stream.emplace_back(s);
    

    Is it possible to do this without the initializer list way?

    Option 1

    Use a list:

        std::list<unseekable_stream> m_stream;
    

    Change the for loop as follows:

    m_stream.emplace_back();
    auto& s = m_stream.back();
    if(is_compressed[i]) s.push(bio::zlib_decompressor());
    s.push(bio::file_source{filenames[i]});
    

    Option 2

    Use unique_ptr:

        std::vector<std::unique_ptr<unseekable_stream>> m_stream;
    

    For loop code:

    auto stream_ptr = std::make_unique<unseekable_stream>();
    ... //same as above but change . to ->
    m_stream.push_back(std::move(stream_ptr));
    

    Option 3

    Initialize vector with size and not use push_back or emplace_back.

    std::vector<unseekable_stream> m_stream;
    
    MultiFileOpener(const std::vector<std::string>& filenames, const std::vector<bool>& is_compressed) 
     : m_stream(filenames.size())
       {
            for(auto i = 0u; i < filenames.size(); i++) {
                unseekable_stream& s = m_stream[i];
                if(is_compressed[i]) s.push(bio::zlib_decompressor());
                s.push(bio::file_source{filenames[i]});
            }
        }
    

    With this, you cannot add or remove streams later. If those features are needed, use the other options.