在不向用户公开库的情况下静态包装库的多态迭代器

Statically wrapping a library's polymorphic iterator without exposing the library to the user

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

不幸的是,在它的接口中,这个库 returns 迭代器作为指针而不是值,因为它们在运行时是多态的。

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

// 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' s型。

有没有我可能错过的技巧?

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 引用常量值时,此类型。

想法是 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>() returns 指向静态局部变量的指针 poly_vtable<T> 实现了每个操作。

Live example.

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

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

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


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

如果您每次回调一次访问 10 个项目,那么调用虚拟方法的开销最多比每次回调少 10 倍。

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