Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this behavior of vector::resize(size_type n) under C++11 and Boost.Container correct?

I have a C++03 application where std::vector<T> types are used throughout as temporary buffers. As such, they often get resized using std::vector<T>::resize() to ensure they are large enough to hold the required data before use. The C++03 prototype for this function is actually:

void resize(size_type n, value_type val = value_type()); 

So in actuality when calling resize(), the vector is enlarged by adding the appropriate number of copies of val. Often, however, I just need to know that the vector is large enough to hold the data I need; I don't need it initialized with any value. Copy-constructing the new values is just a waste of time.

C++11 comes to the rescue (I thought): in its specification, it splits resize() into two overloads:

void resize(size_type n); // value initialization void resize(size_type n, const value_type &val); // initialization via copy 

This fits nicely with the philosophy of C++: only pay for what you want. As I noted, though, my application can't use C++11, so I was happy when I came across the Boost.Container library, which indicates support for this functionality in its documentation. Specifically, boost::container::vector<T> actually has three overloads of resize():

void resize(size_type n); // value initialization void resize(size_type n, default_init_t); // default initialization void resize(size_type n, const value_type &val); // initialization via copy 

In order to verify that I understood everything, I whipped up a quick test to verify the behavior of C++11 std::vector<T> and boost::container::vector<T>:

#include <boost/container/vector.hpp> #include <iostream> #include <vector>  using namespace std; namespace bc = boost::container;  template <typename VecType> void init_vec(VecType &v) {     // fill v with values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]     for (size_t i = 0; i < 10; ++i) v.push_back(i);     // chop off the end of v, which now should be [1, 2, 3, 4, 5], but the other 5 values     // should remain in memory     v.resize(5); }  template <typename VecType> void print_vec(const char *label, VecType &v) {     cout << label << ": ";     for (size_t i = 0; i < v.size(); ++i)     {         cout << v[i] << ' ';     }     cout << endl; }  int main() {     // instantiate a vector of each type that we're going to test     std::vector<int> std_vec;     bc::vector<int> boost_vec;     bc::vector<int> boost_vec_default;      // fill each vector in the same way     init_vec(std_vec);     init_vec(boost_vec);     init_vec(boost_vec_default);      // now resize each vector to 10 elements in ways that *should* avoid reinitializing the new elements     std_vec.resize(10);     boost_vec.resize(10);     boost_vec_default.resize(10, bc::default_init);      // print each one out     print_vec("std", std_vec);     print_vec("boost", boost_vec);     print_vec("boost w/default", boost_vec_default);     } 

Compiling this with g++ 4.8.1 in C++03 mode as follows:

g++ vectest.cc ./a.out 

yields the following output:

std: 0 1 2 3 4 0 0 0 0 0  boost: 0 1 2 3 4 0 0 0 0 0  boost w/default: 0 1 2 3 4 5 6 7 8 9 

This isn't too surprising. I expect the C++03 std::vector<T> to initialize the final 5 elements with zeros. I can even convince myself why boost::container::vector<T> is doing the same (I would assume it emulates C++03 behavior in C++03 mode). I only got the effect that I wanted when I specifically ask for default initialization. However, when I rebuilt in C++11 mode as follows:

g++ vectest.cc -std=c++11 ./a.out 

I get these results:

std: 0 1 2 3 4 0 0 0 0 0  boost: 0 1 2 3 4 0 0 0 0 0  boost w/default: 0 1 2 3 4 5 6 7 8 9 

Exactly the same! Which leads to my question:

Am I wrong in thinking that I should see the same results from each of the three tests in this case? This seems to indicate that the std::vector<T> interface change hasn't really had any effect, as the 5 elements added in the final call to resize() still get initialized with zeros in the first two cases.

like image 588
Jason R Avatar asked Jan 09 '14 18:01

Jason R


People also ask

Can a vector be resized in C++?

C++ Vector Library - resize() FunctionThe C++ function std::vector::resize() changes the size of vector. If n is smaller than current size then extra elements are destroyed. If n is greater than current container size then new elements are inserted at the end of vector.

What does std :: vector resize do?

vector::resizeResizes the container to contain count elements. If the current size is greater than count , the container is reduced to its first count elements.

Can vector be resized?

Vectors are known as dynamic arrays which can change its size automatically when an element is inserted or deleted. This storage is maintained by container.

Does resizing a vector delete data?

Resizing a vector doesn't destroy the values stored in the vector (except for those beyond the new size when shrinking, of course), however growing a vector beyond its capacity will copy (or, in C++11, move) them to a new place, thus invalidating and iterators, pointers or references to those elements.


1 Answers

Not an answer, but a lengthy addendum to Howard's: I use an allocator adapter that basically works the same as Howard's allocator, but is safer since

  1. it only interposes on value-initialization and not all initializations,
  2. it correctly default-initializes.
// Allocator adaptor that interposes construct() calls to // convert value initialization into default initialization. template <typename T, typename A=std::allocator<T>> class default_init_allocator : public A {   typedef std::allocator_traits<A> a_t; public:   template <typename U> struct rebind {     using other =       default_init_allocator<         U, typename a_t::template rebind_alloc<U>       >;   };    using A::A;    template <typename U>   void construct(U* ptr)     noexcept(std::is_nothrow_default_constructible<U>::value) {     ::new(static_cast<void*>(ptr)) U;   }   template <typename U, typename...Args>   void construct(U* ptr, Args&&... args) {     a_t::construct(static_cast<A&>(*this),                    ptr, std::forward<Args>(args)...);   } }; 
like image 119
Casey Avatar answered Sep 22 '22 04:09

Casey