jeudi 3 novembre 2022

Move Assignment Operator for a Custom Filtering Streambuf Class

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