Есть ли способ конвертировать 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()
, чтобы лучше указать, что реализация этих методов более сложна, чем на основе диапазона цикл - отсюда желание избежать копирования вставки реализации.
Ответы
Ответ 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.
}
}