在C ++ 11和Boost.Container下,vector :: resize(size_type n)的行为是否正确?

我有一个C ++ 03应用程序,其中std::vector<T>types被用作临时缓冲区。 因此,它们通常使用std::vector<T>::resize()std::vector<T>::resize()以确保它们足够大,以便在使用前保存所需的数据。 这个函数的C ++ 03原型实际上是:

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

所以实际上在调用resize() ,通过添加val的相应数量的副本来扩大向量。 但是,我经常需要知道vector足够大以容纳我需要的数据; 我不需要用任何值初始化它。 复制构build新值只是浪费时间。

C ++ 11来拯救(我认为):在其规范中,它将resize()分成两个重载:

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

这很适合C ++的哲学:只为你想要的付出代价。 但是,正如我所指出的那样,我的应用程序不能使用C ++ 11,所以当我遇到Boost.Container库的时候,我很高兴,它在文档中指出了对这个function的支持 。 具体来说, boost::container::vector<T>实际上有三个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 

为了validation我所理解的一切,我掀起了一个快速的testing来validationC ++ 11 std::vector<T>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); } 

在C ++ 03模式下用g++ 4.8.1编译,如下所示:

 g++ vectest.cc ./a.out 

产生以下输出:

 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 

这并不令人感到意外。 我期望C ++ 03 std::vector<T>用零初始化最后的5个元素。 我甚至可以说服自己为什么boost::container::vector<T>也是这样做的(我假定它在C ++ 03模式下模拟C ++ 03行为)。 当我特别要求默认初始化时,我只得到了我想要的效果。 但是,当我在C ++ 11模式下重build如下:

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

我得到这些结果:

 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 

一模一样! 这导致我的问题:

我错在认为在这种情况下,我应该从三个testing中的每一个看到相同的结果? 这似乎表明std::vector<T>接口的变化没有任何效果,因为在最后调用resize()添加的5个元素在前两种情况下仍然用零初始化。

不是一个答案,而是霍华德的一个冗长的附录 :我使用一个基本上与霍华德的分配器相同的分配器适配器,但更安全

  1. 它只处理值初始化而不是所有的初始化,
  2. 它正确地默认初始化。
 // 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)...); } }; 

与C ++ 11 resize签名有一个小的function差异,但是您的testing不会公开它。 考虑这个相似的testing:

 #include <iostream> #include <vector> struct X { X() {std::cout << "X()\n";} X(const X&) {std::cout << "X(const X&)\n";} }; int main() { std::vector<X> v; v.resize(5); } 

在C ++ 03下打印:

 X() X(const X&) X(const X&) X(const X&) X(const X&) X(const X&) 

但是在C ++ 11下打印:

 X() X() X() X() X() 

这种变化的动机是更好地支持vector不可复制(仅移动)types。 大部分时间,包括你的情况,这个改变没有什么区别。

有一种方法可以通过使用自定义分配器(您的编译器可能支持,也可能不支持)来实现C ++ 11中的所需function:

 #include <iostream> #include <vector> using namespace std; template <class T> class no_init_alloc : public std::allocator<T> { public: using std::allocator<T>::allocator; template <class U, class... Args> void construct(U*, Args&&...) {} }; template <typename VecType> void init_vec(VecType &v) { // fill v with values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] v.resize(10); for (size_t i = 0; i < 10; ++i) v[i] = i; // Note this change!!! // 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() { std::vector<int, no_init_alloc<int>> std_vec; init_vec(std_vec); std_vec.resize(10); print_vec("std", std_vec); } 

应该输出:

 std: 0 1 2 3 4 5 6 7 8 9 

no_init_alloc只是简单地拒绝做任何初始化,这对于int是很好的,而且没有指定值。 我不得不改变你的init_vec使用赋值来初始化,而不是使用构造。 所以如果你不小心,这可能是危险的/混乱的。 但是它避免了不必要的初始化。

所以实际上在调用resize()的时候,通过添加val的相应数量的副本来扩大向量。 然而,我经常需要知道vector足够大以容纳我需要的数据; 我不需要用任何值初始化它。 复制构build新值只是浪费时间。

不,不是。 拥有一个没有实际构build的元素的容器是没有意义的。 我不确定你期望看到零以外的东西。 未指定/未初始化的元素? 这不是价值初始化的意思。

如果你需要N个元素,那么你应该有N个正确构造的元素,这就是std::vector::resize所做的。 值初始化将零初始化一个没有默认构造函数的对象来调用,所以真的和你想要的相反,这不是安全性和初始化,而是更多。

我build议你真的是std::vector::reserve

这似乎表明std::vector<T>接口的变化没有任何效果

这当然有效果,而不是你正在寻找的那个。 新的resize重载是为了方便起见,当你需要默认或甚至初始化值时,你不必构造你自己的临时表。 容器如何工作并不是一个根本性的改变,也就是说它们总是保持有效的types实例

有效,但在未指定的状态下,如果您离开它们!

未初始化的值

您可以通过创build适当的类来初始化值。 如下所示:

 class uninitializedInt { public: uninitializedInt() {}; uninitializedInt(int i) : i(i) {}; operator int () const { return i; } private: int i; }; 

输出与“boost w / default”相同。

或创build一个自定义的分配器与constructdestroy作为nop。

拆分resize原型

如果void std::vector<T>::resize(size_type n)做了什么void bc::vector<T>::resize(size_type n, default_init_t) ,那么很多旧的有效代码会破坏…

resize()的分割允许调整“仅移动”类的向量,如下所示:

 class moveOnlyInt { public: moveOnlyInt() = default; moveOnlyInt(int i) : i(i) {}; moveOnlyInt(const moveOnlyInt&) = delete; moveOnlyInt(moveOnlyInt&&) = default; moveOnlyInt& operator=(const moveOnlyInt&) = delete; moveOnlyInt& operator=(moveOnlyInt&&) = default; operator int () const { return i; } private: int i; }; 

int值初始化产生0。

int默认初始化并不初始化这个值 – 它只是保留了内存中的任何东西。

resize(10)分配的内存没有被resize(5)释放,或者同一个内存块被重用。 无论哪种方式,你结束了以前的内容。

如果你想使用标准的分配器的vector,这不工作在C + + 11?

  namespace{ struct Uninitialised {}; template<typename T> template<typename U> std::allocator<T>::construct(U* , Uninitialised&&) { /*do nothing*/ }; } template<typename T> void resize_uninitialised(std::vector<T>& vec, std::vector<T>::size_type size) { const Uninitialised* p = nullptr; auto cur_size = vec.size(); if(size <= cur_size) return; vec.reserve(size); //this should optimise to vec.m_size += (size - cur_size); //one cannot help thinking there must be simpler ways to do that. vec.insert(vec.end(), p, p + (size - cur_size)); };