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

2021-12-21 00:00:00 vector c++ c++11 boost

我有一个 C++03 应用程序,其中 std::vector 类型在整个过程中用作临时缓冲区.因此,它们经常使用 std::vector 调整大小,以确保它们足够大以在使用前容纳所需的数据.这个函数的C++03原型其实是:

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());

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

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 来拯救(我认为):在它的规范中,它把 resize() 分成两个重载:

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

这非常符合 C++ 的哲学:只为你想要的付费.不过,正如我所指出的,我的应用程序不能使用 C++11,所以当我遇到 Boost.Container 库时我很高兴,它在其文档中表示支持此功能.具体来说,boost::container::vector实际上有三个resize()的重载:

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

为了验证我是否理解了所有内容,我做了一个快速测试来验证 C++11 std::vectorboost::container 的行为::vector:

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

在 C++03 模式下使用 g++ 4.8.1 编译它如下:

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

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 用零初始化最后 5 个元素.我什至可以说服自己为什么 boost::container::vector 也在做同样的事情(我假设它在 C++03 模式下模拟 C++03 行为).当我特别要求默认初始化时,我才得到我想要的效果.但是,当我在 C++11 模式下重建时如下:

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

我得到了这些结果:

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:

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

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.

推荐答案

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

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. 它只介入值初始化而不是所有初始化,
  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)...);
  }
};

相关文章