Статически оборачивать полиморфный итератор библиотеки, не раскрывая библиотеку пользователю

В настоящее время я интегрирую библиотеку хранилища данных в свое приложение. Мне нужно иметь возможность макетировать это хранилище данных (которое требует интенсивного ввода-вывода) для моих модульных тестов, поэтому создаю оболочку вокруг интерфейса этой библиотеки.

К сожалению, в своем интерфейсе эта библиотека возвращает итераторы как указатели, а не как значения, потому что они полиморфны во время выполнения.

Моя проблема заключается в том, что из-за слоя полиморфизма, который я добавляю, кажется неизбежным добавлять итераторы, которые полиморфны во время выполнения, что приводит к новому уровню косвенности и некоторому более динамическому распределению...

// 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тип.

Есть ли какой-нибудь трюк, который я мог пропустить?

1 ответ

Решение
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); }

пример использования vtable. Вот настройка:

  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.

Та же самая техника может быть расширена для большего количества операций; просто приведение к базе и использование реальных таблиц проще.

Используя это, вы храните poly<IMyIterator, 64, alignof(IMyIterator)>, Это тип значения, содержащий некоторый буфер размером 64 байта.


Другим подходом к сокращению косвенного обращения может стать замена концепции посещения каждого пункта, возможно, повторным посещением в диапазоне.

Если за один обратный вызов вы посещаете диапазон из 10 элементов одновременно, то издержки на вызов виртуальных методов в 10 раз меньше, чем один на один обратный вызов.

Вы можете создавать входные итераторы с объектом диапазона, в котором есть буфер для до 10 элементов, и который автоматически перестраивает его, когда вы достигаете конца, если доступно больше, получая данные в пакетном режиме.

Другие вопросы по тегам