Подтвердить что ты не робот

Есть ли способ конвертировать std::vector <const T *> в std::vector <T *> без дополнительных распределений?

Скажем, у меня есть Storage некоторого Object, который имеет метод, который агрегирует указатели на некоторые из Objects в векторе. Вот так:

class Storage
{
public:
   std::vector<Object*> aggregate_some_objects(); // non-const version
   std::vector<const Object*> aggregate_some_objects() const; // const version

private:
   std::unordered_map<size_t, Object> m_objects; // data is stored 
                                                 // by-value in a non-vector container
}

Как правило, существует способ избежать копирования-вставки в реализации пар const + non-const, вызывая один из них внутри другого с помощью const_cast. Здесь, однако, это невозможно, потому что возвращаемые типы методов различны.

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

Интересно, есть ли способ написать что-то вроде

template <typename T>
std::vector<T*> remove_const_from_vector_of_ptrs(std::vector<const T*>&& input)
{
   std::vector<T*> ret;
   // do some magic stuff here that does not involve
   // more memory allocations
   return ret;
}

Таким образом, позволяя писать

std::vector<const Object*> Storage::aggregate_some_objects() const
{
   // non-trivial implementation
}

std::vector<Object*> Storage::aggregate_some_objects() 
{
   auto objects = const_cast<const Storage*>(this)->aggregate_some_objects();
   return remove_const_from_vector_of_ptrs(std::move(objects));
}

В std::vector (например, std::unique_ptr) не существует метода "release", который позволяет переносить владение памятью - и по очень веской причине, поэтому я ожидаю, что это невозможно.

Я также понимаю, что если бы это было возможно, это была бы опасная операция, которую следует избегать вообще, как const_cast. Но осторожное использование в таких случаях кажется более полезным, чем копирование.

Изменить: добавлены разъяснения к тому, что я подразумеваю под "дополнительными" распределениями и изменил Storage::aggregate_objects() на Storage::aggregate_some_objects(), чтобы лучше указать, что реализация этих методов более сложна, чем на основе диапазона цикл - отсюда желание избежать копирования вставки реализации.

4b9b3361

Ответ 1

Короткий ответ: нет. std::vector<Object*> и std::vector<const Object*> - два разных независимых класса. Они отличаются друг от друга как class A от class B. Часто считается, что только потому, что оба они начинаются с std::vector, они как-то связаны друг с другом. Это неверно, и по этой причине невозможно преобразовать один в другой, "на месте". Каждый из этих векторных классов имеет свой собственный внутренний data() и не будет охотно отдавать его другому странному классу.

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

Один общий подход заключается в том, что как константный, так и метод mutable class являются фасадами для одного, общего, частного шаблона:

// Header file:

class Storage {

public:

    std::vector<const Object*> aggregate_objects() const;

    std::vector<Object*> aggregate_objects();

private:

    template<typename v_type> void make_aggregate_objects(v_type &v) const;
};

// In the translation unit:

template<typename v_type> void Storage::make_aggregate_objects(v_type &v) const
{
     // Now, create 'v' here... v.reserve(), v.push_back(), etc...
}

std::vector<const Object*> Storage::aggregate_objects() const
{
     std::vector<const Object *> v;

     make_aggregate_objects(v);

     return v;
}

std::vector<Object*> Storage::aggregate_objects()
{
     std::vector<const Object *> v;

     make_aggregate_objects(v);

     return v;
}

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

Другим аналогичным подходом является передача лямбда в функцию шаблона вместо передачи векторного объекта с частной функцией шаблона с использованием лямбда-функции в качестве обратного вызова для построения возвращаемого вектора. С небольшим стиранием типа и некоторой помощью от std::function метод частного класса может быть превращен в обычный метод, а не в шаблонный метод.

Ответ 2

Невозможно преобразовать std::vector<const Object*> в std::vector<Object*> без перераспределения памяти и копирования указателей, потому что std::vector является контейнером и имеет свою память.

Использование reinterpret_cast может работать в этом случае, но является undefined поведением и зависит от реализации std::vector:

std::vector<const Object*> const_vec = ...;
std::vector<Object*>& vec = reinterpret_cast<std::vector<Object*>&>(const_vec);

Решение избежать const_cast или ненужных распределений было бы третьей, шаблонной функцией:

template<typename Stor>
static auto Storage::aggregate_objects_(Stor&)
-> std::vector<std::conditional_t<std::is_const<Stor>::value, const Object*, Object*>>
{
    ...
}

где Stor может быть Storage или const Storage.

Тогда aggregate_objects() будет реализован как:

std::vector<const Object*> Storage::aggregate_objects() const {
    return aggregate_objects_(*this);
}

std::vector<Object*> Storage::aggregate_objects() {
    return aggregate_objects_(*this);
}

Ответ 3

Ваши функции возвращаются по значению, поэтому всегда выделяйте в любом случае - о каких "дополнительных распределениях" вы говорите?

Если вы просто храните vector<Object*> внутренне, то тривиально решить вашу проблему:

std::vector<Object*> Storage::aggregate_objects()
{ return m_data; };

std::vector<const Object*> Storage::aggregate_objects() const
{ return std::vector<const Object*>(m_data.begin(), m_data.end()); }

Изменить: в ответ на ваш обновленный вопрос:

Вы не должны писать плохой код, чтобы избежать копирования и вставки тела функции!

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

Ответ 4

Очевидным ответом является no, std::vector<T*> и std::vector<const T*> - это два разных объекта, которые не могут быть заменены.

Однако, вооружившись знаниями внутренних элементов std::vector, должно быть довольно легко увидеть, сохраняем ли мы T* или const T* внутри std::vector, это не имеет значения с точки зрения перспективы.

Следовательно, одним из способов сделать это было бы принудительное преобразование результата из одного типа в другой. Итак:

template <typename T>
std::vector<T*> remove_const_from_result(std::vector<const T*>&& input) {
  return reinterpret_cast<std::vector<T*>&>(input);
}

Большое оговорку с таким подходом заключается в том, что мы можем сделать это только в том случае, если мы достаточно уверены в внутренних компонентах контейнера, который мы применяем для принудительного каста.

Обратите внимание, что это все еще не помогает с вашей исходной проблемой, а именно, что у вас есть две разные функции-члены, и вы, по существу, const_cast -в результате одного на другой.

Чтобы проиллюстрировать, позвольте мне предположить, что у вас есть функция getter без std::vector, поэтому сделайте следующее:

struct Foo {
  const Bar* bar() const { return &bar_; }
  Bar* bar() { return const_cast<Bar*>(bar()); }

 private:
  Bar bar_;
};

Лучшим способом сделать это, ИМО, было бы подвергнуть шаблонную функцию внутренне и использовать ее вместо этого. Таким образом, Foo становится:

struct Foo {
  const Bar* bar() const { return get_internal<const Bar*>(&bar_); }
  Bar* bar() { return get_internal<Bar*>(&bar_); }

 private:
  Bar bar_;
  template <typename T>
  T get_internal(std::remove_const<T>::type ptr) const { return ptr; }
};

Итак, аналогично, для вашего примера вы можете использовать один и тот же подход:

struct Storage {
  std::vector<const Object*> aggregate_objects() const { return aggregate_internal<const Object*>(); }
  std::vector<Object*> aggregate_objects() { return aggregate_internal<Object*>(); }

 private:
  template <typename T>
  std::vector<T*> aggregate_internal() const {
    // actual aggregate function where T* can be const T* also.
  }
}