基于单独的有状态读取器和写入器基础实现ReaderWriter类

时间:2016-09-28 20:29:51

标签: c++ inheritance

假设我有两个班级......

我们可以调用第一个FooReader,它看起来像这样:

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
};

我们可以调用第二个FooWriter,它看起来像这样:

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;
};

这些都非常有效,并按预期完成工作。现在假设我要创建一个FooReaderWriter类。注意

我自然想说这个新类“是”FooReader而“是”FooWriter;接口只是两个类的合并,语义保持不变。我不想重新实现完美的会员功能。

可以使用继承来建模这种关系:

class FooReaderWriter : public FooReader, public FooWriter { };

这很好,因为我得到了共享接口,我得到了实现,并且很好地模拟了类之间的关系。但是有问题:

  • Foo*成员在基类中重复。这是浪费记忆。
  • m_offset成员对于每种基本类型都是独立的,但他们需要共享它(即调用readFooDataAndAdvancewriteFooDataAndAdvance应该推进相同的m_offset成员。

我无法使用PIMPL模式并在其中存储m_foom_offset,因为我将失去基础{{1}中m_foo指针的常量}。class。

在没有重新实现这些类中包含的功能的情况下,我还能做些什么来解决这些问题吗?

2 个答案:

答案 0 :(得分:1)

CRTP。

template<class Storage>
class FooReaderImpl {
public:
  FooData readFooDataAndAdvance() {
    // the point here is that the algorithm is stateful
    // and relies upon the m_offset member
    return get_storage()->m_foo[get_storage()->m_offset++];
  }
private:
  Storage const* get_storage() const { return static_cast<Storage const*>(this); }
  Storage * get_storage() { return static_cast<Storage*>(this); }
};
template<class Storage>
class FooWriterImpl {
public:
  void writeFooDataAndAdvance(const FooData& foodata) {
    // the point here is that the algorithm is stateful
    // and relies upon the m_offset member
    get_storage()->m_foo[get_storage()->m_offset++] = foodata;
  }
private:
  Storage const* get_storage() const { return static_cast<Storage const*>(this); }
  Storage * get_storage() { return static_cast<Storage*>(this); }
};

template<class T>
struct storage_with_offset {
  T* m_foo = nullptr;
  std::size_t m_offset = 0;
};
struct FooReader:
  FooReaderImpl<FooReader>,
  storage_with_offset<const Foo>
{
  FooReader(Foo const* p):
    storage_with_offset<const Foo>{p}
  {}
};
struct FooWriter:
  FooWriterImpl<FooWriter>,
  storage_with_offset<Foo>
{
  FooWriter(Foo* p):
    storage_with_offset<Foo>{p}
  {}
};
struct FooReaderWriter:
  FooWriterImpl<FooReaderWriter>,
  FooReaderImpl<FooReaderWriter>,
  storage_with_offset<Foo>
{
  FooReaderWriter(Foo const* p):
    storage_with_offset<Foo>{p}
  {}
};

如果需要运行时多态性的抽象接口,请从它们继承FooReaderImplFooWriterImpl

现在,FooReaderWriter遵守FooReaderFooWriter的鸭子合约。因此,如果您使用类型擦除而不是继承,它将有资格获得(在使用时)。

我很想将它们改为

using FooReader = std::function<FooData()>;
using FooWriter = std::function<void(FooData const&)>;

然后为std::function实现多重签名FooReaderWriter。但我很奇怪,有点精神错乱。

答案 1 :(得分:1)

这似乎已经为mixin模式做好了准备。我们有最基础的类,只是声明成员:

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

然后我们在它周围写一些包装器来添加阅读:

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

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

并写作:

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

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

然后你只需使用它们:

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