为什么无法分配给本身缺少复制运算符的对象向量?
Why is it impossible to assign to a vector of objects that themselves lack copy operator?
我有一个可复制构造但不可赋值的结构向量:
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>
作为函数的 return 值按值传递。然而,这失败了:
std::vector<TextFragment> v1, v2;
v2 = v1;
错误当然是:
error: C2280: 'Struct &Struct ::operator =(const Struct &)' :
attempting to reference a deleted function
我不明白为什么它会尝试调用它。这是避免重新分配向量的内存块的某种优化吗?..
A std::vector
是一个分配器感知容器。如果我们查看那个 (table 99) 的规范,我们有 a = t
,其中 a
是一个非常量左值, t
是一个左值或常量右值,它需要
T is CopyInsertable into X and CopyAssignable. post: a == t
强调我的
其中 T
是 X
(容器)的 value_type
。它还指出该操作是线性的。由于 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;
}
您可能会从 libc++ 中发现此函数信息丰富。特别要注意对 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())
。
Is that some kind of an optimization to avoid re-allocating the vector's memory block?..
差不多。这是一种优化,可避免重新分配 vector
的 value_type
中可能存在的任何内存块。也就是说,这是一个全局假设,即赋值比销毁后复制构造更有效。
例如考虑 vector<string>
赋值,对于两个相同大小的 vector
s,以及 vector
中每个点的一堆相同大小的 string
s:
v2 = v1;
这个操作所要做的就是每个 memcpy
string
。根本没有分配。减少 allocations/deallocations 是当今最重要的优化之一。
但是,您的 Struct
并没有失去一切。你想要做的是指示 vector
你不想分配你的 Struct
,而是销毁它们,然后从 v1
复制构造它们。这样做的语法是:
v2.clear();
for (const auto& x : v1)
v2.push_back(x);
如以下评论所述,您还可以复制构造 v1
,然后使用副本 swap
。您要么需要创建 v1
的临时本地副本,要么需要在成员交换的 "lhs" 上使用 v1
:
std::vector<Struct>(v1).swap(v2);
我个人觉得这很难读。在 C++11/14 中,我更喜欢这种涉及移动赋值的替代方案:
v2 = std::vector<Struct>(v1);
这些备选方案更美观。但第一种选择,使用 clear()
和 push_back
平均来说效率最高。这是因为第一个备选方案是唯一有机会在 v2
中重用 capacity()
的备选方案。另外两个 always 在 v1
的副本中重新创建新的 capacity()
并丢弃 v2
现有的 capacity()
.
我有一个可复制构造但不可赋值的结构向量:
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>
作为函数的 return 值按值传递。然而,这失败了:
std::vector<TextFragment> v1, v2;
v2 = v1;
错误当然是:
error: C2280: 'Struct &Struct ::operator =(const Struct &)' : attempting to reference a deleted function
我不明白为什么它会尝试调用它。这是避免重新分配向量的内存块的某种优化吗?..
A std::vector
是一个分配器感知容器。如果我们查看那个 (table 99) 的规范,我们有 a = t
,其中 a
是一个非常量左值, t
是一个左值或常量右值,它需要
T is CopyInsertable into X and CopyAssignable. post: a == t
强调我的
其中 T
是 X
(容器)的 value_type
。它还指出该操作是线性的。由于 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;
}
您可能会从 libc++ 中发现此函数信息丰富。特别要注意对 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())
。
Is that some kind of an optimization to avoid re-allocating the vector's memory block?..
差不多。这是一种优化,可避免重新分配 vector
的 value_type
中可能存在的任何内存块。也就是说,这是一个全局假设,即赋值比销毁后复制构造更有效。
例如考虑 vector<string>
赋值,对于两个相同大小的 vector
s,以及 vector
中每个点的一堆相同大小的 string
s:
v2 = v1;
这个操作所要做的就是每个 memcpy
string
。根本没有分配。减少 allocations/deallocations 是当今最重要的优化之一。
但是,您的 Struct
并没有失去一切。你想要做的是指示 vector
你不想分配你的 Struct
,而是销毁它们,然后从 v1
复制构造它们。这样做的语法是:
v2.clear();
for (const auto& x : v1)
v2.push_back(x);
如以下评论所述,您还可以复制构造 v1
,然后使用副本 swap
。您要么需要创建 v1
的临时本地副本,要么需要在成员交换的 "lhs" 上使用 v1
:
std::vector<Struct>(v1).swap(v2);
我个人觉得这很难读。在 C++11/14 中,我更喜欢这种涉及移动赋值的替代方案:
v2 = std::vector<Struct>(v1);
这些备选方案更美观。但第一种选择,使用 clear()
和 push_back
平均来说效率最高。这是因为第一个备选方案是唯一有机会在 v2
中重用 capacity()
的备选方案。另外两个 always 在 v1
的副本中重新创建新的 capacity()
并丢弃 v2
现有的 capacity()
.