Почему невозможно назначить вектор объектов, которым не хватает оператора копирования?
У меня есть вектор структур, которые можно скопировать, но не назначать:
struct Struct
{
inline Struct(const std::string& text, int n) : _text(text), _n(n) {}
inline Struct(const Struct& other) : _text(other._text), _n(other._n) {}
const std::string _text;
const int _n;
Struct& operator=(const Struct&) = delete;
};
Все работало нормально. Фактически, я мог бы даже передать std::vector<Struct>
вокруг по значению в качестве возвращаемого значения функции. И все же это не удается:
std::vector<TextFragment> v1, v2;
v2 = v1;
Ошибка, конечно, такова:
error: C2280: 'Struct & Struct:: operator = (const Struct &)': попытка ссылки на удаленную функцию
Я не понимаю, почему он пытается это сделать. Это какая-то оптимизация, чтобы избежать перераспределения блока векторной памяти?..
Ответы
Ответ 1
Это какая-то оптимизация, чтобы избежать перераспределения блока векторной памяти?..
Почти. Оптимизация позволяет избежать перераспределения всех блоков памяти в vector
value_type
. То есть, это глобальное предположение, что назначение может быть более эффективным, чем разрушение, за которым следует построение копии.
Например, рассмотрите назначение vector<string>
для двух равных размеров vector
s и пучка равного размера string
в каждом месте в vector
:
v2 = v1;
Вся эта операция должна выполняться memcpy
каждый string
. Никаких ассигнований вообще. Сокращение распределения/освобождения является одной из самых важных оптимизаций, которые существуют сегодня.
Однако для вашего Struct
все не потеряно. То, что вы хотите сделать, - это указать vector
, что вы не хотите назначать свои Struct
s, но вместо этого уничтожаете их, а затем скопируйте их из v1
. Синтаксис для этого:
v2.clear();
for (const auto& x : v1)
v2.push_back(x);
Как отмечено в комментариях ниже, вы также можете скопировать конструкцию v1
, а затем swap
с копией. Вам либо нужно создать временную локальную копию v1
, либо вам нужно использовать v1
в "lhs" члена swap:
std::vector<Struct>(v1).swap(v2);
Мне лично это трудно читать. В С++ 11/14 я предпочитаю эту альтернативу, которая включает назначение переноса:
v2 = std::vector<Struct>(v1);
Эти альтернативы легче на глаза. Но первая альтернатива, использующая clear()
и push_back
, будет наиболее эффективной в среднем. Это связано с тем, что первая альтернатива - это единственная возможность повторного использования capacity()
в v2
. Остальные два всегда воссоздают новый capacity()
в копии v1
и выбрасывают v2
существующий capacity()
.
Ответ 2
A std::vector
является контейнером, поддерживающим распределитель. Если мы посмотрим на спецификацию для этого (таблица 99), мы имеем a = t
, где a
является неконстантным lvalue, а t
является значением lvalue или const rvalue и требует
T является CopyInsertable в X и CopyAssignable. post: a == t
Акцент на мине
Где t
- value_type
of X
(контейнер). В нем также указывается, что операция является линейной. Поскольку t
должен быть CopyAssignable и Struct
не является CopyAssignable, от него не требуется работать.
Это означает, что операция присваивания будет выглядеть примерно так:
std::vector<T>& operator=(const std::vector<T>& rhs)
{
// allocate enough room for data if needed
for (std::size_t i = 0; i < rhs.size(); ++i)
data[i] = rhs.data[i];
return *this;
}
Ответ 3
Вы можете найти эту функцию информативной, из libС++. В частности, обратите внимание на вызов std::copy
.
template <class _Tp, class _Allocator>
template <class _ForwardIterator>
typename enable_if
<
__is_forward_iterator<_ForwardIterator>::value &&
is_constructible<
_Tp,
typename iterator_traits<_ForwardIterator>::reference>::value,
void
>::type
vector<_Tp, _Allocator>::assign(_ForwardIterator __first, _ForwardIterator __last)
{
size_type __new_size = static_cast<size_type>(_VSTD::distance(__first, __last));
if (__new_size <= capacity())
{
_ForwardIterator __mid = __last;
bool __growing = false;
if (__new_size > size())
{
__growing = true;
__mid = __first;
_VSTD::advance(__mid, size());
}
pointer __m = _VSTD::copy(__first, __mid, this->__begin_);
if (__growing)
__construct_at_end(__mid, __last, __new_size - size());
else
this->__destruct_at_end(__m);
}
else
{
deallocate();
allocate(__recommend(__new_size));
__construct_at_end(__first, __last, __new_size);
}
}
operator =
просто вызывает assign(r.begin(), r.end())
после исправления распределителя, если они отличаются.