Создайте контейнер производных указателей из исходного контейнера базовых указателей - PullRequest
2 голосов
/ 12 января 2020

Как эффективно вернуть вектор производных указателей из вектора базовых указателей?

std::vector<const Base*> getb();
std::vector<const Derived*> getd()
{
  auto vb = getb(); /// I know for a fact all vb elements point to Derived
  return ...;
}

Derived не наследуется напрямую от Base

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

boost :: range?

Ответы [ 2 ]

0 голосов
/ 12 января 2020

Во-первых, я бы сказал, что если вы столкнетесь с этой проблемой, вы должны выяснить, почему в вашем проекте вы должны выполнить этот шаг. Возможно, есть что-то, что вы могли бы сделать по-другому, чтобы избежать этой проблемы. Лично я считаю подозрительным, что вы генерируете контейнер Base*, каждый из которых указывает только на Derived объекты.

Но если вы хотите сделать это, есть несколько возможностей, как go об этом. Если B не является виртуальным базовым классом D, везде вы можете использовать static_cast вместо dynamic_cast из-за [expr.stati c .cast] / 11 (Короче говоря, если вы теперь, когда dynamic_cast будет работать, вы также можете static_cast). Это сэкономит вам проверку во время выполнения в dynamic_cast.

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

Вы в основном создаете второй вектор и копируете все указатели:

const auto vb = get_b();
std::vector<const Derived*> ret;
ret.reserve(vb.size());
std::transform(cbegin(vb), cend(vb), std::back_inserter(ret), [](const Base* p) { return dynamic_cast<const Derived*>(p); });
return ret;

Это, на мой взгляд, самый быстрый и краткий способ сделать это только с помощью stl в C ++ 14. Я не очень разбираюсь в возможностях наддува. Если вы можете использовать какой-либо итератор преобразования, вы можете инициализировать ret непосредственно из двух итераторов.

Нет дополнительной памяти, шаблонный код, небольшие затраты времени выполнения доступа

Оберните ваш std::vector<const Base*> в собственный класс, который работает как вектор и возвращает const Derived* при доступе. С dynamic_cast это приведет к небольшим накладным расходам времени выполнения при доступе, так как он должен будет выполнить проверку. Если вы можете использовать static_cast (как говорилось выше), это не будет иметь место. (Вы можете иметь очень небольшие накладные расходы из-за дополнительного уровня косвенности).

Мне очень нравится это решение, и я бы лично использовал его. Я не уверен, что в boost есть какой-то контейнерный адаптер, в противном случае вам придется написать немного стандартного кода, чтобы иметь векторный интерфейс (вы могли наследовать от std::vector и только перезаписывать operator[] и at(), но это имеет свои проблемы, поскольку std::vector не имеет виртуальных методов, включая деструктор!).

Нет памяти и нет накладных расходов времени выполнения

Тебе бы это действительно понравилось, я думаю ^^. Я не думаю, что это возможно с std::vector. Без лишних затрат времени выполнения вам потребуется вернуть объект, который действительно имеет тип std::vector<const Derived*>. Без лишних затрат памяти вы должны перезапустить память возвращаемого объекта get_b.

Но std::vector<T> не имеет возможности отказаться от владения sh владением своей собственной памятью (за исключением другого std::vector<T> в том же T путем замены или перемещения конструкции / назначения). Может быть, вы можете сделать что-то подозрительное с помощью специального распределителя, чтобы базовое хранилище не удалялось при уничтожении вектора, и вы получаете его до уничтожения через data(). Но это похоже на идеальный способ получить утечку памяти. Тем более, что я действительно не знаю, как это работает с емкостью по сравнению с размером.

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

Можно обойти эту проблему go, используя std::unique_ptr<T[]> вместо std::vector. По сути, это компромисс между std::array и std::vector. Он содержит массив размера времени выполнения, но размер этого массива является постоянным после его выделения. Здесь вы можете получить хранилище с помощью release() и создать из него новое std::unique_ptr без проблем.

Это приводит нас к худшей проблеме. Следующий код недействителен. Вы не можете даже разыграть от Derived** до Base** (и давайте даже не будем говорить об обратном)

std::unique_ptr<const Base*[]> base(static_cast<const Base**>(new Derived*[5]));
std::unique_ptr<const Derived*[]> dev(dynamic_cast<const Derived**>(base.release()));

Я понятия не имею, существует ли какой-то странный способ переосмысления всего целого кусок памяти как указатели другого типа. Даже тогда, если это каким-то образом разумно сделать. Поэтому я не вижу способа сделать этот вариант.

0 голосов
/ 12 января 2020

Я точно знаю, что все элементы vb указывают на Derived

. Лучшее направление действий - express это утверждение с типами . Почему getb() возвращает вектор базовых указателей, если вы знаете лучший тип для элементов? Сделайте его вектором производных указателей с самого начала.

В противном случае вам нужно dynamic_cast каждый отдельный указатель в vb и поместить результат в другой контейнер. Другие броски могут или не могут работать.

...