Ковариантная обёртка для std :: vector - PullRequest
3 голосов
/ 28 мая 2019

Я хочу ковариантную оболочку для std::vector.Моя идея заключалась в том, чтобы сделать что-то вроде этого:

  • Создать абстрактный базовый класс BaseVector<B>, чьи методы begin и end просто перенаправить в чисто виртуальные функции.
  • Создатьбетон производного класса DerivedVector<B, D>, который окутывает std::vector<D>.Его begin и end методы будут затенять базовый класс, и он будет реализовывать виртуальные методы, к которым базовый класс перенаправляет.

Таким образом, если у вас есть указатель на BaseVector вы можете перебирать экземпляры базового класса, а если у вас есть указатель DerivedVector, вы можете перебирать экземпляры производного класса.

( EDIT : очевидно, что BaseVector не обязательноподдержка вставки, так как он не может знать тип объектов, которые он содержит. Возможно, это означает, что «Вектор» не лучшее название для него, я открыт для предложений. Спасибо @ CygnusX1.)

Для класса DerivedVector методы begin и end могут просто пересылать std::vector.

Вопрос: Как я могу реализовать методы BaseVector begin и end вперед?Нужно ли мне писать свой собственный класс итераторов, охватывающий std::vector итераторов?

В качестве альтернативы: Есть ли лучший или более простой способ сделать это?

A *Экземпляр 1042 * необходимо использовать через указатель BaseVector<B> в коде, который знает о B, но не D, и он должен иметь эквивалентную производительность для простого удержания вектора производного класса (если вызывающий код знал о производномclass)

Пример использования :

Библиотека предоставляет класс BaseWidget и класс BaseWidgetPool, из которых происходит пользователь.Из-за особенностей библиотеки в любой данной программе будет ровно один класс DerivedWidget, но этот класс будет отличаться для каждой программы, использующей библиотеку.

Класс BaseWidgetPool включает методы, которые выполняют итерациювсе виджеты в пуле и использовать их BaseWidget функциональность.Но каждая программа DerivedWidgetPool, вероятно, захочет использовать функциональность DerivedWidget своего содержимого.

(я знаю, что я мог бы также справиться с этим, создав универсальный класс WidgetPool<T>, но я бы лучше инкапсулировалT к той части кода, которая фактически его использует.)

1 Ответ

3 голосов
/ 28 мая 2019

С диапазонами это не проблема.Он имеет все необходимые API для полиморфных диапазонов:

#include <range/v3/all.hpp>

namespace rv = ranges::view;

int main() {
    std::vector<Derived> derived_container;

    // ... fill it

    ranges::any_view<Base*> base_view =
        rv::all(derived_container) | rv::transform([](auto& e) -> Base* { return &e; });

    // do stuff with `base_view`
}

Тогда вы можете использовать base_view до тех пор, пока жив derived_container.

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

Если вы не можете использовать ни стандартный диапазон, ни диапазон v3, я бы создал симиаларную оболочку.Таким образом, вместо того, чтобы выставлять иерархию классов, вы скрываете ее в служебном классе.

Живой пример

Конечно, any_view существует только для того, чтобы избежать шаблонов.функция над типом диапазона при передаче его.Вместо этого вы можете напрямую материализовать диапазон в вектор:

auto base_view =
    rv::all(derived_container) | rv::transform([](auto& e) -> Base* { return &e; });

std::vector<Base*> base_container = ranges::to<std::vector>(base_view);

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

...