Reputation: 3256
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:
Upvotes: 1
Views: 142
Reputation: 27664
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.
Upvotes: 1