为什么无法分配给本身缺少复制运算符的对象向量?

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

强调我的

其中 TX(容器)的 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?..

差不多。这是一种优化,可避免重新分配 vectorvalue_type 中可能存在的任何内存块。也就是说,这是一个全局假设,即赋值比销毁后复制构造更有效。

例如考虑 vector<string> 赋值,对于两个相同大小的 vectors,以及 vector 中每个点的一堆相同大小的 strings:

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() 的备选方案。另外两个 alwaysv1 的副本中重新创建新的 capacity() 并丢弃 v2 现有的 capacity().