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

Jas*_*n R 23 c++ boost vector c++11

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

void resize(size_type n, value_type val = value_type());
Run Code Online (Sandbox Code Playgroud)

因此,实际上在调用时resize(),通过添加适当数量的副本来放大矢量val.但是,通常我只需要知道它vector足够大以容纳我需要的数据; 我不需要用任何值初始化它.复制构造新值只是浪费时间.

C++ 11拯救了(我想):在它的规范中,它分为resize()两个重载:

void resize(size_type n); // value initialization
void resize(size_type n, const value_type &val); // initialization via copy
Run Code Online (Sandbox Code Playgroud)

这非常适合C++的理念:只需支付你想要的东西.正如我所指出的那样,我的应用程序不能使用C++ 11,所以当我遇到Boost.Container库时,我很高兴,它表明在其文档中支持这个功能.具体来说,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
Run Code Online (Sandbox Code Playgroud)

为了验证我理解了所有内容,我进行了快速测试以验证C++ 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);    
}
Run Code Online (Sandbox Code Playgroud)

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

g++ vectest.cc
./a.out
Run Code Online (Sandbox Code Playgroud)

产生以下输出:

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
Run Code Online (Sandbox Code Playgroud)

这并不太令人惊讶.我希望C++ 03 std::vector<T>用零初始化最后的5个元素.我甚至可以说服自己为什么boost::container::vector<T>这样做(我认为它模仿C++ 03模式中的C++ 03行为).当我特别要求默认初始化时,我只得到了我想要的效果.但是,当我在C++ 11模式下重建时,如下所示:

g++ vectest.cc -std=c++11
./a.out
Run Code Online (Sandbox Code Playgroud)

我得到这些结果:

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
Run Code Online (Sandbox Code Playgroud)

完全相同的!这引出了我的问题:

我错误地认为在这种情况下我应该从三个测试中看到相同的结果吗?这似乎表明std::vector<T>接口更改并没有真正产生任何影响,因为在最后一次调用中添加的5个元素resize()仍然在前两种情况下用零初始化.

Cas*_*sey 58

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

  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)...);
  }
};
Run Code Online (Sandbox Code Playgroud)

  • http://en.cppreference.com/w/cpp/container/vector/resize现在链接到这里.这很酷 :) (8认同)
  • PS:我替换了Casey的分配器并得到了与我的答案相同的结果.凯西的分配器应该具有与我相同的性能,并且更安全. (5认同)
  • @TrentP`std :: allocator_traits <T> :: construct(Alloc&a,U*u,Args && ... args)`委托给`a.construct(u,args ...)`如果它是有效的,否则是相当于`:: new(u)U(args ...)`.由于调用者应该使用`allocator_traits`,`std :: allocator`不需要实现一个等同于回退的成员`construct` - 因此它被弃用了.这里实现的适配器使用`allocator_traits`而不是直接在底层分配器上调用`construct`,所以即使在调整不再实现`construct`的`std :: allocator`时它也能继续正常工作. (4认同)

How*_*ant 20

还有就是用C++ 11个小功能上的差异resize签名,但您的测试不会揭露它.考虑这个类似的测试:

#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);
}
Run Code Online (Sandbox Code Playgroud)

在C++ 03下打印:

X()
X(const X&)
X(const X&)
X(const X&)
X(const X&)
X(const X&)
Run Code Online (Sandbox Code Playgroud)

但在C++ 11下它打印:

X()
X()
X()
X()
X()
Run Code Online (Sandbox Code Playgroud)

此更改的动机是更好地支持不可复制(仅限移动)类型vector.大多数时候,包括在你的情况下,这种变化没有任何区别.

有一种方法可以使用自定义分配器(您的编译器可能支持或不支持)在C++ 11中实现您想要的功能:

#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);
}
Run Code Online (Sandbox Code Playgroud)

哪个应该输出:

std: 0 1 2 3 4 5 6 7 8 9 
Run Code Online (Sandbox Code Playgroud)

no_init_alloc简单地拒绝做任何初始化,这是罚款int,有一个未确定的值离开它.我不得不改变你init_vec的使用赋值来初始化而不是使用构造.如果你不小心,这可能是危险/混乱.但是它确实避免了不必要的初始化.