I have written a "filtering streambuf" class (FilterStreambuf below). Only stream output is considered. The end goal is a std::ostream object which filters, writes to a std::ofstream, and has a move assignment operator.
My problem and question is what is the idiomatic correct implementation of a move assignment operator for StreamBase (below)? Specifically, if:
std::swap(this->sb, that.sb);
is called in
void StreamBase::swap(StreamBase& that)
Then the FilterOFStream output streambuf pointers are mismatched after the move assignment:
FilterOFStream::ofs.rdbuf() != FilterOFStream::fos.sb
Example printout from move assignment demonstrating mismatched streambuf pointer addresses.
this:0000003D3E2FF278 // correctly equal prior to swap
0000003D3E2FF278
that:0000003D3E2FF6C0
0000003D3E2FF6C0
this:0000003D3E2FF6C0 // mismatched after swap
0000003D3E2FF278
that:0000003D3E2FF278
0000003D3E2FF6C0
And the program crashes later when a write attempt is made, e.g.:
FilterStreambuf::sb->sputn()
Whereas if the sb member swap is commented out, the program does not crash, but I worry I'm doing this wrong.
//std::swap(this->sb, that.sb);
So is commenting out this streambuf* swap the correct solution? Or am I doing this completely wrong? Any advice for better code structure is appreciated. Minimum reproducible code below.
#include <streambuf>
#include <vector>
#include <iostream>
#include <filesystem>
#include <fstream>
struct StreamBase : public std::streambuf {
std::streambuf* sb = nullptr;
StreamBase(std::streambuf* sb) : sb(sb) { }
StreamBase& operator=(StreamBase&& that) {
this->swap(that);
return *this;
}
void swap(StreamBase& that) {
using std::swap;
std::streambuf::swap(that);// Exchanges the 6 pointers and Locale.
std::swap(this->sb, that.sb);// to swap or not to swap ? that is the question
}
friend void swap(StreamBase& a, StreamBase& b) {
a.swap(b);
}
};
struct FilterStreambuf : public StreamBase {
FilterStreambuf(std::streambuf* sb) : StreamBase(sb){ /* setp(), etc. */ }
~FilterStreambuf() {sync(); }
int_type overflow(int_type c) final { /*does stuff*/ return traits_type::not_eof(c); }
std::streamsize xsputn(const char_type*, std::streamsize) final { /*does stuff*/ return 0; }
int sync() noexcept final { /*does stuff*/ return 0; }
};
struct FilterOStream : public FilterStreambuf, public std::ostream {
FilterOStream(std::streambuf* sb)
:
FilterStreambuf(sb),
std::ostream(this)
{ }
FilterOStream& operator=(FilterOStream&& that) {
this->swap(that);
return *this;
}
void swap(FilterOStream& that) {
FilterStreambuf::swap(that);
std::ostream::swap(that);// calls std::basic_ios::swap. swaps all but rdbuf()
//std::ostream::rdbuf(this);// AFAIK unnecessary because rdbuf not changed
}
friend void swap(FilterOStream& a, FilterOStream& b) {
a.swap(b);
}
};
std::ofstream MakeOFStream(std::filesystem::path file) {
if (file.empty())
return std::ofstream();
else
return std::ofstream(file, std::ios::binary | std::ios::app);
}
struct FilterOFStream {
std::ofstream ofs;
FilterOStream fos;// use this
FilterOFStream()// invalid stream is always available
:
FilterOFStream(std::filesystem::path())
{ }
FilterOFStream(std::filesystem::path file)
:
ofs(MakeOFStream(file)),
fos(ofs.rdbuf())
{ }
void Print(std::string name)const {
std::cerr << name << ":" << (void*)fos.sb << "\n " << (void*)ofs.rdbuf() << "\n";
}
FilterOFStream& operator=(FilterOFStream&& that) {
Print("this");
that.Print("that");
using std::swap;
swap(ofs, that.ofs);
swap(fos, that.fos);
Print("this");
that.Print("that");
return *this;
}
};
int main(int argc, char* argv[]) {
FilterOFStream a;
a = FilterOFStream("foo.txt");
}
Aucun commentaire:
Enregistrer un commentaire