指向 std::vector 和 std::list 元素的指针

Posted

技术标签:

【中文标题】指向 std::vector 和 std::list 元素的指针【英文标题】:Pointers to elements of std::vector and std::list 【发布时间】:2011-03-18 07:09:53 【问题描述】:

我有一个std::vector,其中包含某些类ClassA 的元素。此外,我想使用 std::map<key,ClassA*> 创建一个索引,它将一些键值映射到指向向量中包含的元素的指针。

当元素被添加在向量的末尾(不是插入)时,是否有任何保证这些指针仍然有效(并指向同一个对象)。即,以下代码是否正确:

std::vector<ClassA> storage;
std::map<int, ClassA*> map;

for (int i=0; i<10000; ++i) 
  storage.push_back(ClassA());
  map.insert(std::make_pair(storage.back().getKey(), &(storage.back()));

// map contains only valid pointers to the 'correct' elements of storage

如果我使用std::list 而不是std::vector,情况如何?

【问题讨论】:

这里vector的作用是什么?您需要记住它们的创建顺序吗?您可以改用 map 和 vecor 。对映射元素的迭代器/指针/引用的有效期更长。查看您最喜欢的标准库参考的保证。 【参考方案1】:

Vectors - 不。因为向量的容量永远不会缩小,所以即使元素被删除或更改,只要它们引用被操作元素之前的位置,就可以保证引用、指针和迭代器仍然有效。但是,插入可能会使引用、指针和迭代器无效。

列表 - 是的,插入和删除元素不会使指向其他元素的指针、引用和迭代器失效

【讨论】:

答案应该颠倒,向量 -> 否和列表 -> 是,因为问题是“是否可以保证这些指针仍然有效?” deque 也可能是一个不错的选择,如果需要随机访问并且在添加元素时不需要重新分配。 @MartinStettner: §23.2.1.3 保证虽然进入出队的迭代器无效,但指向元素的指针和引用仍然有效:An insert in the middle of the deque invalidates all the iterators and references to elements of the deque. An insert at either end of the deque invalidates all the iterators to the deque, but has no effect on the validity of references to elements of the deque. 我无法从标准中找到一个引用,它保证向量的容量不能缩小——这可能不是直接要求,而是其他要求的影响,因为它的复杂性算法。您能否提供一个报价/理由,为什么该标准要求向量容量永不缩小? --这是我所知道的所有实现的行为,但这与标准的保证不同。 只是一个注释;提及在 N3337 中的 23.3.3.4 #1, p.737【参考方案2】:

据我了解,没有这样的保证。向向量中添加元素将导致元素重新分配,从而使地图中的所有指针无效。

【讨论】:

我就是这么想的。你知道std::list吗?毕竟如果实现为链表,就不需要重新分配了…… 我认为 can cause 是更合适的措辞。即便如此,我可以期待 realloc 在内部实现中被使用,它再次可以破坏指针。【参考方案3】:

使用std::deque!仅使用 push_back() 时,指向元素的指针是稳定的。

注意:元素的迭代器可能会失效!指向元素的指针不会。

编辑:此答案解释了原因的详细信息:C++ deque's iterator invalidated after push_front()

【讨论】:

你确定吗? C++ 标准中是否有任何部分涵盖此声明?它可能大部分时间都以这种方式实现,但我需要某种保证...... 为什么迭代器,它基本上是一个专门为那个容器设计的指针,应该被无效,而不是一个原始指针,它只代表一个内存地址(和一个类型)? @mxp:迭代器需要能够找到下一个元素。此功能需要迭代器中的附加信息,并且此附加信息可能会失效。 @mxp:看到这个答案:***.com/questions/1658956/… 我将标准中的引用添加到this答案作为评论。【参考方案4】:

我不确定它是否得到保证,但实际上storage.reserve(needed_size) 应该确保不会发生重新分配。

但是为什么不存储索引呢? 通过将索引添加到开始迭代器 (storage.begin()+idx) 很容易将索引转换为迭代器,并且通过首先取消引用它然后获取其地址 (&amp;*(storage.begin()+idx)) 很容易将任何迭代器转换为指针。

【讨论】:

问题是,我事先不知道needed_size(我承认代码有点简化......)存储索引是一个选项,但我还需要传递指向程序的其他各个部分不应访问向量(同样代码未显示该方面) @MartinStettner:您可以轻松地将索引转换为向量的指针。我已经扩展了我的答案来解释。 整个东西被封装成一个类,需要将指针传递给“外部”,程序的其他部分也可能存储这些指针,所以它们需要是常量。如果我使用您的方法,我还需要提供 begin() 迭代器,这将违反封装(向量存储应该是内部实现细节......)。【参考方案5】:

只需让它们都存储指针,并在不需要时显式删除对象。

std::vector<ClassA*> storage;
std::map<int, ClassA*> map;

for (int i=0; i<10000; ++i) 
  ClassA* a = new ClassA()
  storage.push_back(a)
  map.insert(std::make_pair(a->getKey(), a))

// map contains only valid pointers to the 'correct' elements of storage

【讨论】:

我强烈建议不要将裸指针存储在 STL 容器中。这是泄漏的秘诀。 嗯,这正是我试图避免的:)。在这种情况下我只能使用地图(对于我的问题),我只想有一些容器来处理内存释放。 感谢编辑(在 iPad 上,无法格式化或放入分号)。 我同意 sbi。使用shared_ptr&lt;&gt; 应该不会有这个缺点。 @mxp:虽然我站在你的立场上(除非在紧密循环中运行,否则额外的分配很可能不会导致性能下降),事实是向量以块的形式执行内存分配,它们成倍增长。这意味着对内存分配器的调用量将是对数的,而不是与向量的增长呈线性关系。如果你添加了一个共享指针,它复制了所需的分配数量——除非你使用make_shared【参考方案6】:

从一个 cmets 到另一个答案,似乎您想要的只是集中(简化)内存管理。如果情况确实如此,您应该考虑使用像boost pointer container 库这样的预打包解决方案,并让您自己的代码尽可能简单。

特别是看ptr_map

【讨论】:

非常感谢您指出这一点。不幸的是,这个项目是为一个大客户准备的,他(还)不想将 boost 库包含到他的代码中(尽管这会缓解很多问题 :) ...)【参考方案7】:
    矢量编号

    对于列表是的。 如何? 迭代器用作指向列表中特定节点的指针。 因此您可以将值分配给任何结构,例如:

    列出我的列表;

    对 temp;

    temp = make_pair(mylist.begin(), x);

【讨论】:

这与DumbCoder's的答案几乎相同,只是晚了7年,而且各方面都更糟 我遇到了类似的问题,正在寻找一个简单的例子。由于上述任何答案中都没有,我想自己写一个例子。

以上是关于指向 std::vector 和 std::list 元素的指针的主要内容,如果未能解决你的问题,请参考以下文章

指向std :: vector和std :: list元素的指针

指向 std::vector 的指针,指针声明

我是不是保证在移动向量后指向 std::vector 元素的指针有效?

迭代时在 std::vector 中保存指向不同类型的指针

指向基类的指针的 std::vector 的深拷贝

在基于范围的 for 循环中从 std::vector<Object> 获取指向 Object 的指针