【问题标题】:Resizing a C++ std::vector<char> without initializing data [duplicate]在不初始化数据的情况下调整 C++ std::vector<char> 的大小[重复]
【发布时间】:2023-03-30 15:20:01
【问题描述】:

对于向量,可以假设元素连续存储在内存中,允许将范围 [&vec[0], &vec[vec.capacity()) 用作普通数组。例如,

vector<char> buf;
buf.reserve(N);
int M = read(fd, &buf[0], N);

但现在向量不知道它包含 M 个字节的数据,由 read() 外部添加。我知道 vector::resize() 设置了大小,但是它也清除了数据,所以在 read() 之后不能用来更新大小打电话。

有没有一种简单的方法可以将数据直接读入向量并在之后更新大小?是的,我知道一些明显的解决方法,比如使用一个小数组作为临时读取缓冲区,并使用 vector::insert() 将其附加到向量的末尾:

char tmp[N];
int M = read(fd, tmp, N);
buf.insert(buf.end(), tmp, tmp + M)

这行得通(这就是我今天正在做的事情),但让我感到困扰的是,如果我可以将数据直接放入向量。

那么,在外部添加数据时,有没有一种简单的方法可以修改矢量大小?

【问题讨论】:

  • 您确定&amp;buf[0] 在调试模式下工作吗?例如,在 Visual Studio 上,在调试模式下 std::vector::operator[] 执行范围检查。因此,如果 buf 为空,则该表达式将抛出。
  • 我使用 GCC,并通过 valgrind 运行程序以确保没有发生内存错误。我只能说,使用 GNU libstdc++ 实现,这是可行的。 &vec[0] 似乎给你一个指向保留内存的直接指针,不管 size()。
  • @user984228:如果您乐于依赖 GCC 的实现细节(这是一个 BAD IDEA (TM)),那么您可以查看其实现 vector 的源代码。你可以看到它存储beginend 指针和容量的位置,如果你只是覆盖end 指针,我很确定这会改变你想要的大小。只要在容量足够大的情况下复制resize() 的实现,就可以忽略memset/fill/whatever。当然,您必须解决一些 private 修饰符,也许通过在偏移量中进行硬编码。
  • @SteveJessop:我刚刚死了一点。
  • @Matthieu:相当。如果所有这些听起来都是个坏主意,那么希望依靠 GCC 似乎可以让您写入仅保留而不是调整大小的空间这一事实,这听起来也是个坏主意 :-)

标签: c++ stl vector resize


【解决方案1】:

另一个较新的问题是这个问题的副本,有an answer,看起来就像这里问的一样。这是它的副本(v3)以供快速参考:

初始化无法关闭是已知问题 明确为std::vector

人们通常会实现自己的pod_vector&lt;&gt;,但不会这样做 任何元素的初始化。

另一种方法是创建一个与 char 布局兼容的类型, 其构造函数什么都不做:

struct NoInitChar
{
    char value;
    NoInitChar() {
        // do nothing
        static_assert(sizeof *this == sizeof value, "invalid size");
        static_assert(__alignof *this == __alignof value, "invalid alignment");
    }
};

int main() {
    std::vector<NoInitChar> v;
    v.resize(10); // calls NoInitChar() which does not initialize

    // Look ma, no reinterpret_cast<>!
    char* beg = &v.front().value;
    char* end = beg + v.size();
}

【讨论】:

    【解决方案2】:

    看起来你可以在 C++11 中做你想做的事(虽然我自己没有尝试过)。您必须为向量定义一个自定义分配器,然后使用emplace_back()

    首先,定义

    struct do_not_initialize_tag {};
    

    然后用这个成员函数定义你的分配器:

    class my_allocator {
        void construct(char* c, do_not_initialize_tag) const {
            // do nothing
        }
    
        // details omitted
        // ...
    }
    

    现在您可以在不初始化的情况下向数组中添加元素:

    std::vector<char, my_allocator> buf;
    buf.reserve(N);
    for (int i = 0; i != N; ++i)
        buf.emplace_back(do_not_initialize_tag());
    int M = read(fd, buf.data(), N);
    buf.resize(M);
    

    这个效率取决于编译器的优化器。例如,循环可能会将 size 成员变量增加 N 次。

    【讨论】:

      【解决方案3】:

      写入size()th 元素及其之后是未定义的行为。

      下一个示例以 c++ 的方式将整个文件复制到一个向量中(无需知道文件的大小,也无需在向量中预先分配内存):

      #include <algorithm>
      #include <fstream>
      #include <iterator>
      #include <vector>
      
      int main()
      {
          typedef std::istream_iterator<char> istream_iterator;
          std::ifstream file("example.txt");
          std::vector<char> input;
      
          file >> std::noskipws;
          std::copy( istream_iterator(file), 
                     istream_iterator(),
                     std::back_inserter(input));
      }
      

      【讨论】:

      • 当然你也可以提前调用reserve 指定文件大小以避免所有的重新分配。
      【解决方案4】:
      vector<char> buf;
      buf.reserve(N);
      int M = read(fd, &buf[0], N);
      

      此代码片段调用未定义的行为。你不能写超过size()元素,即使你已经保留了空间

      正确的代码是这样的:

      vector<char> buf;
      buf.resize(N);
      int M = read(fd, &buf[0], N);
      buf.resize(M);
      


      PS。您的陈述“使用向量,可以假设元素连续存储在内存中,允许范围 [&amp;vec[0], &amp;vec[vec.capacity()) 用作普通数组”不是真的。允许的范围是 [&amp;vec[0], &amp;vec[vec.size())

      【讨论】:

      • 有没有办法避免第一次resize()引起的不必要的初始化?
      • 99% 的肯定额外的初始化将与您的 I/O 成本相形见绌。
      • @user984228:问题是这是否是个问题。如果您已经测量并且初始化成为瓶颈(我不希望这样),那么您可能需要考虑实现自己的数据结构...注意:当且仅当,我不想您是否实现了自己的数据类型,而是意识到在大多数情况下这不会成为性能瓶颈——即无论您从哪里读取,都可能比初始化的成本慢得多。
      • @MarkB:你不希望一个好的插入(范围)实现专门用于随机迭代器的单个保留调用吗?
      • @user984228: Then I'd rather just use the temporary buffer + insert. It should be at least as efficient, 不正确。临时缓冲区避免了零初始化,读入缓冲区,然后需要从缓冲区复制到向量。 Vector resize 初始化为零,然后读入向量。零初始化至少与副本一样快,可能更快。因此,调整大小仍然比缓冲区快。
      【解决方案5】:

      您的程序片段已进入未定义行为的领域。

      buf.empty() 为真时,buf[0] 具有未定义的行为,因此&amp;buf[0] 也是未定义的。

      这个片段可能做你想做的事。

      vector<char> buf;
      buf.resize(N); // preallocate space
      int M = read(fd, &buf[0], N);
      buf.resize(M); // disallow access to the remainder
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-04
        • 1970-01-01
        • 2013-12-02
        • 2017-05-18
        • 2011-07-26
        • 2019-05-18
        相关资源
        最近更新 更多