OMGtechy OMGtechy - 2 months ago 6
C++ Question

Implementing a ReaderWriter class based upon separate stateful Reader and Writer bases

Suppose I have two classes...

We can call the first

FooReader
and it looks something like this:

class FooReader {
public:
FooReader(const Foo* const foo)
: m_foo(foo) {

}

FooData readFooDataAndAdvance() {
// the point here is that the algorithm is stateful
// and relies upon the m_offset member
return m_foo[m_offset++];
}

private:
const Foo* const m_foo;
size_t m_offset = 0; // used in readFooDataAndAdvance
};


We can call the second
FooWriter
and it looks something like this:

class FooWriter {
public:
FooWriter(Foo* const foo)
: m_foo(foo) {

}

void writeFooDataAndAdvance(const FooData& foodata) {
// the point here is that the algorithm is stateful
// and relies upon the m_offset member
m_foo[m_offset++] = foodata;
}

private:
Foo* const m_foo;
size_t m_offset = 0;
};


These both work wonderfully and do their job as intended. Now suppose I want to create a
FooReaderWriter
class. Note that the

I naturally want to say that this new class "is a"
FooReader
and "is a"
FooWriter
; the interface is simply the amalgamation of the two classes and the semantics remain the same. I don't want to reimplement perfectly good member functions.

One could model this relationship using inheritance like so:

class FooReaderWriter : public FooReader, public FooWriter { };


This is nice because I get the shared interface, I get the implementation and I nicely model the relationship between the classes. However there are problems:


  • The
    Foo*
    member is duplicated in the base classes. This is a waste of memory.

  • The
    m_offset
    member is separate for each base type, but they need to share it (i.e. calling either
    readFooDataAndAdvance
    and
    writeFooDataAndAdvance
    should advance the same
    m_offset
    member).



I can't use the PIMPL pattern and store
m_foo
and
m_offset
in there, because I'd lose the const-ness of the
m_foo
pointer in the base
FooReader
class.

Is there anything else I can do to resolve these issues, without reimplementing the functionality contained within those classes?

Answer

This seems ready made for the mixin pattern. We have our most base class which just declares the members:

template <class T>
class members {
public:
    members(T* f) : m_foo(f) { }
protected:
    T* const m_foo;
    size_t m_offset = 0;
};

and then we write some wrappers around it to add reading:

template <class T>
struct reader : T {
    using T::T;

    Foo readAndAdvance() {
        return this->m_foo[this->m_offset++];
    };
};

and writing:

template <class T>
struct writer : T {
    using T::T;

    void writeAndAdvance(Foo const& f) {
        this->m_foo[this->m_offset++] = f;
    }
};

and then you just use those as appropriate:

using FooReader = reader<members<Foo const>>;
using FooWriter = writer<members<Foo>>;
using FooReaderWriter = writer<reader<members<Foo>>>;
Comments