使用std容器的稳定内存地址(如vector,list,queue,...)

时间:2014-05-26 13:49:08

标签: c++ pointers c++11 vector

注意:我没有意识到指针被认为是迭代器,因此有人可能会认为我所谓的缺少内存地址稳定性应该被称为< em>迭代器失效。请阅读副本,以获得更抽象和更健全的问题。

我的问题与此问题有关:C++ reference changes when push_back new element to std::vector

我想使用一组对象,为简单起见,这些对象在内存中只存在一次。因此,我想使用一个容器,如std :: vector,来存储所有对象一次。然后我将使用指向其他结构中的对象的指针。不幸的是,std :: vector可能会改变它元素的内存地址,因此使用指向这些元素的指针是不明确的。我需要指针,因为我想使用其他结构来引用这些对象,比如std :: priority_queue或其他std数据结构。

在特定情况下,对象是图算法中连接的标签,这意味着它们是在整个算法中创建的,因此无法预先分配。这意味着std :: vector是不够的,因为它可能会重新定位其内容,使指向std :: priority_queues或其他数据结构中可能存在的这些标签的指针无效。

但是,我需要标签的唯一时刻就是它们的创建时间或我可以从包含数据结构以外的数据结构访问它们。因此,我永远不需要从容器中获取第n个元素,我只需要能够将对象保留在堆栈或堆上,并在创建它们时获取指针以在其他结构中使用它。最后,当容器从堆栈中弹出时,其中的元素需要很好地清理。我认为std :: list可能是合适的,因为我对抽象链表的知识永远不需要重新分配;允许稳定指针

但是,我无法在std :: lists中找到此指针稳定性的任何地方。也许有一些优秀的东西,一些容器类正是我想要的。当然,我总是可以使用 new ,将所有指针追加到std :: list并迭代在最后执行删除。但这不是我喜欢的方式,因为它需要更多的内存管理,因为我认为应该只需要稳定指针

问题: std :: list 指针是否稳定?有比std :: list更好的解决方案吗?

为了说明问题,我也做了这个例子:http://ideone.com/OZYeuw。用std :: vector替换std :: list,行为变得不确定。

#include <iostream>
#include <list>
#include <queue>
#include <vector>

struct Foo {
    Foo(int _a) : a(_a) {}
    int a;
};

struct FooComparator {
    bool operator()(Foo *a, Foo *b) { return a->a < b->a; }
};

int main() {
    std::list<Foo> foos;
    //std::vector<Foo> foos; // when used instead, the behaviour will become undefined
    std::priority_queue<Foo *, std::vector<Foo *>, FooComparator> pq;

    // Simulate creation and 'containment' of objects, while they are being processed by other structures.
    for(int i=0; i<100; ++i) {
        foos.push_back(Foo((100-i) % 10));
        pq.emplace(&foos.back());
    }

    while(not pq.empty()) {
        std::cout << pq.top()->a << " "; // the dereference -> may segfault if foos is not *pointer stable*
        pq.pop();
    }

    std::cout << std::endl;
    return 0;
}

2 个答案:

答案 0 :(得分:14)

对于所有标准容器,指针/引用和迭代器失效都有特定的规则。如果您可以预测最大容量,则偶数std::vector<T>可能是一个选项:

  • std::vector<T>末尾添加/删除对象可使指针和迭代器保持稳定,除非std::vector<T>需要重新分配其内部结构。也就是说,指针和迭代器只有在添加对象和v.size() == v.capacity()时才会失效。您可以使用v.reserve(n)预留空间。
  • std::deque<T>的任意一端添加/删除对象可使指针保持稳定,但会使迭代器无效。
  • std::list<T>中的任何位置添加/删除对象可使指针和迭代器保持稳定。

显然,删除对象的指针和迭代器在所有情况下都是无效的。但是,指向其他对象的指针和迭代器遵守上述规则。

操作和内存的开销按容器显示的顺序增加。也就是说,理想情况下,您使用std::vector<T>并提前分配足够的内存以保持对象稳定。如果您无法预测所需的最大大小,std::deque<T>是下一个最佳选项:std::deque<T>是一个固定大小的数组数组,即每个对象的开销相对较小且相对较少内存分配。只有当你需要保留指针和迭代器表时,才能预测容器的大小,std::list<T>是一个合理的选择。要降低每个对象的开销,可以考虑使用std::forward_list<T>,它具有与std::list<T>相同的失效约束,但只能在一个方向上遍历,并且使用起来更不方便。

我使用具有足够预留内存的std::vector<T>。如果我不能,我会这样做,以便我可以使用std:vector<T>。只有在真的不可能的情况下,我才会考虑使用std::deque<T>来存储对象。 ......我很少使用std::list<T>因为几乎没有理由使用它。

答案 1 :(得分:3)

是的,插入列表不会使任何迭代器无效(指针是迭代器),擦除元素只会使表示该元素的迭代器失效。

cplusplus.com对于标准容器的每个成员函数都有迭代器有效性部分,例如:http://www.cplusplus.com/reference/list/list/insert/。这些声明通常基于标准,但如果您有疑问,可以查看。标准状态保证成员函数的形式如下:&#34;效果:...不影响迭代器和引用的有效性。&#34;