静态包装库的多态迭代器,而不向用户公开库



我目前正在将数据存储库集成到我的应用程序中。我需要能够为我的单元测试模拟此数据存储(I/O 密集型(,从而在该库的接口周围创建一个包装器。

不幸的是,在其接口中,此库将迭代器作为指针而不是值返回,因为它们在运行时是多态的。

我的问题是,由于我添加的多态层,似乎不可避免地在运行时添加多态的迭代器,因此会产生新的间接级别和一些更动态的分配......

// Library code
class LibIterator
{
// pure virtual methods
};
class LibDataStore
{
LibIterator* getIt();
};
// My interface
class IMyIterator{
// pure virtual methods
};
class MyLibIterator : public IMyIterator
{
std::unique_ptr<LibIterator> m_iterator;
};
class MyIterator
{
std::unique_ptr<MyLibIterator> m_iterator;
};
class IMyDataStore
{
MyIterator getIt();
};

这是一大堆取消引用的指针,每次使用迭代器的任何方法的虚拟调度,加上每个迭代器创建的至少 2 个动态分配(lib 迭代器 + 我的(......

我正在考虑使用 CRTP 来帮助解决这个问题,但我无法找到一种方法来防止代码使用IMyDataStore来查看迭代器的具体实现MyIterator的类型。

我可能错过了什么技巧吗?

template<class T, std::size_t sz, std::size_t algn>
struct poly {

如果你还不害怕,你应该

poly_vtable<T> const* vtable=0;
std::aligned_storage_t<sz, algn> data;

我们可以稍后介绍 vtable。

T* get() { return vtable->get(&data); }
T const* get() const { return vtable->get((void*)&data); }

虚拟表格的使用示例。 这是设置:

template<class U, class...Args>
U* emplace(Args&&...args){
static_assert(sizeof(U)<=sz && alignof(U)<=algn, "type too large");
clear();
U* r = ::new((void*)&data) U(std::forward<Args>(args)...);
vtable = get_poly_vtable<T,U>();
return r;
}

复制:

poly(poly const& o){
if (!o.vtable) return;
o.vtable->copy( &data, &o.data );
vtable=o.vtable;
}
poly(poly&& o){
if (!o.vtable) return;
o.vtable->move( &data, &o.data );
vtable=o.vtable;
}
poly& operator=(poly const& rhs) {
if (this == &rhs) return *this;
clear();
if (!rhs.vtable) return *this;
rhs.vtable->copy( &data, &rhs.data );
vtable = rhs.vtable;
return *this;
}
poly& operator=(poly&& rhs) {
if (this == &rhs) return *this;
clear();
if (!rhs.vtable) return *this;
rhs.vtable->move( &data, &rhs.data );
vtable = rhs.vtable;
return *this;
}

破坏:

void clear(){
if (!vtable) return;
vtable->dtor(&data);
vtable=nullptr;
}
~poly(){clear();}

类似指针的操作:

explicit operator bool()const{return vtable;}
T& operator*(){ return *get();}
T const& operator*() const{ return *get();}
T* operator->(){ return get();}
T const* operator->() const{ return get();}

从派生自 T 的类型构造:

template<class U,
class dU=std::decay_t<U>,
class=std::enable_if_t<!std::is_same<dU, poly>{}>,
class=std::enable_if_t<std::is_base_of<T, dU>{}>
>
poly(U&& u) {
emplace<std::decay_t<U>>( std::forward<U>(u) );
}
};

请注意,当 const 引用 const 值时,此类型。

这个想法是poly<T>是类型T的多态值。 它有大小限制。

您可以使用T*vtable 来安排其他操作的多态性。

template<class T>
struct poly_vtable{
T*(*get)(void*)=0;
void(*copy)(void*,void const*)=0;
void(*move)(void*,void*)=0;
void(*dtor)(void*)=0;
};
template<class T, class U>
poly_vtable<T> make_poly_vtable() {
return {
[](void* ptr)->T*{ return static_cast<U*>(ptr); },
[](void* dest, void const* src){ ::new(dest) U(*static_cast<U const*>(src)); },
[](void* dest, void* src){ ::new(dest) U(std::move(*static_cast<U*>(src))); },
[](void* ptr){ static_cast<U*>(ptr)->~U(); }
};
}
template<class T, class U>
poly_vtable<T> const* get_poly_vtable() {
static const auto r = make_poly_vtable<T,U>();
return &r;
}

get_poly_vtable<T,U>()返回一个指针,指向已实现每个操作的静态本地poly_vtable<T>

活生生的例子。

现在,您可以拥有基于 vtable 的多态值类型。

同样的技术可以扩展到更多的操作;简单地投射到基地并使用真正的vtables更容易。

使用它,您可以存储一个poly<IMyIterator, 64, alignof(IMyIterator)>. 这是一个包含 64 字节缓冲区的值类型。


减少间接访问的另一种方法是用可能重复的范围访问取代每个项目访问的概念。

如果每次回调一次访问 10 个项目的范围,则调用虚拟方法的开销比每次回调少 10 倍。

您可以使用范围对象创建输入迭代器,该对象中最多包含 10 个项目的缓冲区,并且在您到达末尾时自动重建它,如果有更多可用,则批量获取数据。

最新更新