【问题标题】:How can I get the address of the buffer allocated by vector::reserve()?如何获取 vector::reserve() 分配的缓冲区地址?
【发布时间】:2010-07-28 12:32:30
【问题描述】:

我有一个 std::vector 值,我知道它的最大大小,但实际大小在使用过程中会有所不同:

void setupBuffer(const size_t maxSize) {
  myVector.reserve(maxSize);
}

void addToBuffer(const Value& v) {
  myVector.push_back(v);

  if (myVector.size() == maxSize) {
    // process data...
    myVector.clear();
  }
}

但是,在 setupBuffer 中,我需要获取指向 myVector 数据开头的指针。我正在使用第三方库,我必须在其中预先缓存此指针,以便在“处理数据...”部分进行的调用中使用。

void setupBuffer(const size_t maxSize) {
  myVector.reserve(maxSize);

  cachePtr(&(myVector[0])); // doesn't work, obviously
}

我不想预先 resize() 向量,因为我想使用 vector.size() 来表示添加到向量中的元素数。

那么,有没有办法在分配(reserve())之后但在它有任何元素之前获取指向向量缓冲区的指针?我会想象缓冲区存在(只要我限制 push_back'd 值的数量就不会移动)......也许这不能保证?

【问题讨论】:

  • 为什么需要缓存指针,而不是在需要时安全读取?
  • 我正在使用第 3 方库进行 I/O 到特定文件格式。它的工作方式是通过发送指针和最大大小在库中设置“缓冲区”。然后你可以异步调用例如'write' 将数据刷新到磁盘。我可以在每次达到 maxSize 时设置指针,但这将是额外的开销(我必须测试多少)。不过,好点子。
  • 请注意,此问题询问如何获取指针,但在您拥有该指针后,您可以做什么 的答案会有所不同。有关直接涵盖该问题的问题,请参阅Is accessing the raw pointer after std::vector::reserve safe?

标签: c++ stl vector


【解决方案1】:

除非您超出了保留的容量,否则在调用 reserve 后向量缓冲区将不会移动。您的问题是获取指向第一个元素的指针。显而易见的答案是将单个假条目推送到向量中,获取指向它的指针,然后将其删除。

如果库接受函子而不是指针,则更好的方法是,当它需要访问缓冲区时它会调用它 - 您可以让函子推迟获取地址,直到缓冲区有一些真实的内容。但是,我知道你没有重写库的奢侈。

【讨论】:

  • 是的,这就是我正在做的。谢谢。
  • 好主意,+1 来自我。我想从技术上讲这仍然是未定义的行为,但实际上它可能适用于每个实现。
  • @sbi 我不明白为什么这会是 UB - 任何参考?
  • 它不比任何 push_back 或 resize 操作更 UB。也就是说,它不是UB。
  • @sbi: 如果你得到一个指向假元素的指针,移除假元素,然后插入一个真实元素,你得到的指向假元素的指针可以安全地用于参考真实元素(参见§3.8/7)。当然,如果您尝试在删除假元素和插入真实元素之间取消引用指针,那么您肯定有未定义的行为。
【解决方案2】:

没有。不允许访问 fector 中索引大于size 的任何元素。在这里调整大小是您唯一的选择。

你可以做的是这样的:

myvec.resize(SOME_LARGE_VALUE);
myLibrary(&myVec[0]);
myvec.resize(GetSizeUsedByLibrary());

当你调整大小时,向量中的元素不会被破坏,除了那些索引高于新大小的元素。低于 resize 中设置的数字的元素将被单独保留。 Example using std::basic_string, but equally applicable to vector

【讨论】:

  • 是的,这就是我目前正在做的事情:myVector.reserve(maxSize); myVector.resize(1);缓存指针(&myVector[0]); myVector.clear();似乎工作。只是希望有一种更清洁的方法。谢谢。
  • @Ryan:那是无效的。大概 C API cachePtr 访问了向量的第一个元素,这意味着你有未定义的行为。对于有效行为,您必须保持低于size(),而不是capacity()。它似乎有效;但未定义行为的有效答案是“似乎有效”。
【解决方案3】:

你试过front()吗?此外,您可以 push_back 一个元素,像以前一样获取地址,然后删除它。

所有这些都不能保证,但你可以阅读你的向量的来源,看看它是否适合你,如果你必须这样做。你也可以很容易地滚动你自己的向量,它有一个指向数据的指针并且永远不会移动它。

【讨论】:

  • 不确定 front() 与向量中的 [0] 有何不同,至少基于文档。因为它返回一个引用,所以它必须至少有一个构造对象。
  • 我想我认为是储备创建了它,而 [0] 抱怨访问一个不存在的元素。我想知道 front() 是否检查了边界(我查看的文档没有说)。您可以添加和删除元素。
【解决方案4】:

它周围有很多黑客攻击。但我推荐你有效的方式 - 覆盖分配器,第二个模板参数:

typedef std::vector<MyItem, MyAllocator> myvector_t;

在 MyAllocator 之后提供固定的缓冲区分配,并将这个分配器实例作为向量构造函数的参数传递

【讨论】:

  • -1 表示未定义的行为。向量在调整大小时将默认构造元素,这将破坏向量旧大小之上的内容,即使使用分配器也是如此。
  • 有趣的建议,但分配并不是真正的问题。更多的是关于如何从 std::vector 中获取指针的 API 问题......不确定自定义分配器是否会有所帮助。
  • @Billy ONeal - 不,如果大小是众所周知的,那么你应该分配一次缓冲区 - 在分配器的构造函数中。之后必须防止重新分配(例如通过断言)。所有分配都应在分配器的初始缓冲区内执行。
  • @Ryan - 指向缓冲区的指针不是由向量提供的,而是由分配器提供的。
  • @Dewfy:是的,使用分配器可以确保缓冲区不会移动。但是当你在向量上调用resize 来修复大小时,它会默认构建整个范围,破坏你的缓冲区。
猜你喜欢
  • 1970-01-01
  • 2014-05-30
  • 2018-09-07
  • 2013-06-09
  • 1970-01-01
  • 1970-01-01
  • 2021-06-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多