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

Stable memory addresses using a std container (like vector, list, queue, ...)

本文关键字:地址 内存 list 容器 std 使用 vector queue      更新时间:2023-10-16

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

我的问题与这个有关:c++引用在push_back new element到std::vector时发生了变化。

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

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

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

然而,我找不到任何地方表明指针稳定性对于std::lists是正确的。也许有一些更好的东西,一些容器类正好能满足我的需求。当然,我总是可以使用new,将所有指针附加到std::list中,并在末尾迭代执行delete操作。但这不是我喜欢的方式,因为它需要更多的内存管理,因为我认为应该只需要获得稳定指针

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

为了说明这个问题,我还做了这个例子:http://ideone.com/OZYeuw。将std::列表替换为std::vector,则该行为变为未定义。

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

对于所有标准容器,指针/引用和迭代器失效都有特定的规则。如果您可以预测最大容量,甚至可以选择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>,因为几乎没有任何理由使用它。

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

cplusplus.com对每个标准容器的成员函数都有section 迭代器有效性,例如:http://www.cplusplus.com/reference/list/list/insert/。这些说法通常是基于标准的,但如果你有疑问,你可以检查一下。标准以这样的形式声明成员函数的保证:"Effects:…不影响迭代器和引用的有效性。"