Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it impossible to assign to a vector of objects that themselves lack copy operator?

I have a vector of structures that are copy-constructible, but non-assignable:

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;
};

It all worked fine. In fact, I could even pass the std::vector<Struct> around by value as a return value of a function. And yet, this fails:

std::vector<TextFragment> v1, v2;
v2 = v1;

The error, of course, is:

error: C2280: 'Struct &Struct ::operator =(const Struct &)' : attempting to reference a deleted function

I don't understand why it tries to invoke that. Is that some kind of an optimization to avoid re-allocating the vector's memory block?..

like image 750
Violet Giraffe Avatar asked May 18 '16 20:05

Violet Giraffe


People also ask

What happens if you use the default copy constructor for vector?

The default copy constructor will copy all members – i.e. call their respective copy constructors. So yes, a std::vector (being nothing special as far as C++ is concerned) will be duly copied.

Does vector make a copy?

At the time of declaration of vector, passing an old initialized vector copies the elements of the passed vector into the newly declared vector. They are deeply copied.

Can we assign a value to a vector?

vector:: assign() is an STL in C++ which assigns new values to the vector elements by replacing old ones. It can also modify the size of the vector if necessary.


2 Answers

Is that some kind of an optimization to avoid re-allocating the vector's memory block?..

Almost. It is an optimization to avoid re-allocating whatever memory blocks might be present in vector's value_type. That is, it is a global assumption that assignment can be more efficient than destruction followed by copy construction.

For example consider vector<string> assignment, for two equal sized vectors, and a bunch of equal sized strings in each spot in the vector:

v2 = v1;

All this operation has to do is memcpy each string. No allocations at all. Reducing allocations/deallocations is one of the most important optimizations that exist today.

However, all is not lost for your Struct. What you want to do is instruct the vector that you do not wish to assign your Structs, but instead destruct them, and then copy construct them from v1. The syntax for doing this is:

v2.clear();
for (const auto& x : v1)
    v2.push_back(x);

As noted in the comments below, you can also copy construct v1, and then swap with the copy. You either need to create a temporary local copy of v1, or you need to use v1 on the "lhs" of member swap:

std::vector<Struct>(v1).swap(v2);

I personally find this hard to read. In C++11/14 I prefer this alternative which involves move assignment:

v2 = std::vector<Struct>(v1);

These alternatives are easier on the eyes. But the first alternative, using clear() and push_back is going to be the most efficient on average. This is because the first alternative is the only one that has a chance of reusing capacity() in v2. The other two always recreate new capacity() in the copy of v1 and throw away v2's existing capacity().

like image 153
Howard Hinnant Avatar answered Sep 21 '22 12:09

Howard Hinnant


A std::vector is a allocator-aware container. If we look at the spec for that(table 99) we have a = t where a is a non-const lvalue and t is a lvalue or const rvalue and it requires

T is CopyInsertable into X and CopyAssignable. post: a == t

Emphasis mine

Where T is the value_type of X(the container). It also states that the operation is linear. Since T needs to be CopyAssignable and Struct is not CopyAssignable it is not required to work.

This implies that the assignment operation would look something like:

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;
}
like image 22
NathanOliver Avatar answered Sep 20 '22 12:09

NathanOliver