假设我有两个班级......
我们可以调用第一个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
成员对于每种基本类型都是独立的,但他们需要共享它(即调用readFooDataAndAdvance
和writeFooDataAndAdvance
应该推进相同的m_offset
成员。 我无法使用PIMPL模式并在其中存储m_foo
和m_offset
,因为我将失去基础{{1}中m_foo
指针的常量}。class。
在没有重新实现这些类中包含的功能的情况下,我还能做些什么来解决这些问题吗?
答案 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}
{}
};
如果需要运行时多态性的抽象接口,请从它们继承FooReaderImpl
和FooWriterImpl
。
现在,FooReaderWriter
遵守FooReader
和FooWriter
的鸭子合约。因此,如果您使用类型擦除而不是继承,它将有资格获得(在使用时)。
我很想将它们改为
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>>>;