Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is std::vector<T>::resize( n, val ) sufficient for initialisation?

This is a C++11-specific question. Assume I have a vector std::vector<T> v already used, and that I want to resize it to n elements initialised with an existing value T val. (Typical usecase: vector is the member of an instance being recycled).

What are the pros/cons of the following ways, and which is most efficient?

1) Is std::vector<T>::resize( n, val ) sufficient for initialisation?

v.clear();
v.resize( n, val );

2) If not, then I assume the following is correct?

v.clear();
v.resize(n);
std::fill( v.begin(), v.end(), val );

3) How about swapping?

v.swap( std::vector<T>( n, val ) );
like image 465
Jonathan H Avatar asked Jan 13 '15 14:01

Jonathan H


4 Answers

Why not use the interface that's designed for exactly this job?

v.assign(n, val);

Documentation Here

like image 165
Richard Hodges Avatar answered Oct 27 '22 01:10

Richard Hodges


(4)

std::fill(v.begin(), std::min(v.begin() + n, v.end()), val);
v.resize(n, val);

If T has suitable assignment behavior which is at least cheaper than constructing a new one, then use (4). This is the case for T = int (assign and construct are the same) and T = std::string (assigning can be faster than constructing, as it may be able to use existing buffer).

If T has the same cost of assignment and construction (eg T = int), then (1) could also be used for clarity without loss of performance.

If T cannot be assigned or, for some reason, assigning is more costly than constructing (rare) then use (1)

(1) could be simplified by using v.assign(n, val); (kudos to @Casey)

I don't know if (4) would be the same performance-wise using assign. I don't know if assign will (ironic, given the name) assign the new elements to existing ones, or construct them anew.

Edit: A possible improvement to (4), which I haven't tested. It can avoid the overhead of copying/moving during a change in vector capacity.

if (n <= v.capacity())
{
    std::fill(v.begin(), std::min(v.begin() + n, v.end()), val);
    v.resize(n, val);
}
else
{
    v.assign(n, val);
}
like image 39
Neil Kirk Avatar answered Oct 27 '22 02:10

Neil Kirk


(1) is enough. (3) also works. The difference is that (1) doesn't free the memory if new size is less than current. (3) always allocates new chunk of memory and deletes the old one.

(2) is slower than (1) in general, because it first default-constructs the elements, then assigns them. It may even not compile if T is not default-constructible.

like image 3
Anton Savin Avatar answered Oct 27 '22 02:10

Anton Savin


Lets break it down to show the differences between each of these. I'll use n as the new size, m as the old size.

1.

v.clear();//keeps the same buffer, but calls the destructor on all the values
v.resize(n, val);//only makes a new buffer if the value is bigger, does no moves.

2.

v.clear();//keeps the same buffer, but calls the destructor on all the values
v.resize(n);//initializes all the values to default
std::fill( v.begin(), v.end(), val );//initializes all the values again to the new value

3.

v.swap( std::vector<T>( n, val ) );//calls destructor on all values in v, cannot reuse the buffer, initializes all the values to the new value

The differences are subtle but real. 1 can (but is not guarenteed to) reuse the buffer, which can save on memory overhead. 2 is the same as 1 but does a double reinitialize. 3 is the same as 1 but it cannot reuse the buffer.

Personally I think that the differences between the three is too subtle to matter in most cases, and 1 is the most readable.

like image 3
IdeaHat Avatar answered Oct 27 '22 00:10

IdeaHat