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