【问题标题】:Does std::vector change its address? How to avoidstd::vector 是否更改其地址?如何避免
【发布时间】:2010-03-15 13:26:07
【问题描述】:

由于向量元素是连续存储的,我猜在一些 push_back 之后它可能没有相同的地址,因为初始分配的空间不够。

我正在编写需要引用向量中元素的代码,例如:

int main(){
    vector<int> v;
    v.push_back(1);
    int *ptr = &v[0];
    for(int i=2; i<100; i++)
        v.push_back(i);
    cout << *ptr << endl; //?
    return 0;
}

ptr 不一定包含对v[0] 的引用,对吧?怎么保证?

我的第一个想法是使用指针向量和动态分配。我想知道是否有更简单的方法可以做到这一点?

PS.:实际上我使用的是类的向量而不是 int,但我认为问题是相同的。

【问题讨论】:

  • 为什么不只在需要时获取&amp;v[0]
  • 您可以保留元素的索引而不是指针,然后使用 at() 或 operator[] 访问它
  • @KennyTM 虽然:上面没有明确说明,但在我的实际代码中,我无法确定在需要时访问哪个索引。
  • @Dmitry Yudakov:好主意!不幸的是,我意识到使用这种方法需要我做很多更改,因为我已经在代码的其他部分使用了带有指针的结构,但是由于我预先分配了向量,所以没有问题。

标签: c++ stl vector


【解决方案1】:

不要使用 reserve 来推迟这个悬空指针错误 - 作为一个遇到同样问题的人,耸耸肩,保留 1000,然后几个月后花了很长时间试图找出一些奇怪的内存错误(向量容量超过 1000),我可以告诉你这不是一个稳健的解决方案。

您希望避免获取向量中元素的地址,如果可能的话,正是因为重新分配的不可预测性。如果必须,使用迭代器而不是原始地址,因为检查过的 STL 实现会在它们变得无效时告诉你,而不是随机崩溃。

最好的解决办法是改变你的容器:

  • 您可以使用 std::list - 它不会在添加元素时使现有迭代器失效,并且在擦除时只有被擦除元素的迭代器才会失效
  • 如果您使用 C++0x,std::vector<:unique_ptr>> 是一个有趣的解决方案
  • 或者,使用指针和 new/delete 也不错 - 只是不要忘记在删除指针之前删除它们。以这种方式正确并不难,但您必须非常小心,不要因为忘记删除而导致内存泄漏。 (Mark Ransom 还指出:这不是异常安全的,如果异常导致向量被破坏,整个向量内容就会泄露。)
  • 请注意,boost 的 ptr_vector 不能安全地与某些 STL 算法一起使用,这可能会给您带来问题。

【讨论】:

  • 带有 new/delete 的指针会在一个向量在你不期望的情况下被销毁时出现问题——例如,由于抛出异常。
  • IMO:std::list 的用法很少。我会推荐 std::deque 。
【解决方案2】:

您可以通过调用其reserve成员函数来增加vector使用的底层数组的容量:

v.reserve(100);

只要在向量中放入的元素不超过 100 个,ptr 就会指向第一个元素。

【讨论】:

  • 由于reserve 保留至少与您要求的一样多,因此您最多可以将capacity 元素放入向量中。
  • 现在的问题是不知道会占用多少空间。使用足够高的值是个好主意吗?在某些情况下,只有 10^6 是安全的,但在其他情况下,100 就足够了。
  • 如果您不知道要存储多少元素,那么保留是个坏主意!当向量超过该容量时,您只是将悬空指针错误推迟到以后!
  • 如果您事先不知道要插入多少个元素,请使用容器中的索引或 Jerry Coffin 建议的智能指针。
【解决方案3】:

如何保证是个好办法?

std::vector&lt;T&gt; 保证是连续的,但实现可以在更改向量内容的操作上重新分配或释放存储空间(向量迭代器、指针或对元素的引用也变得未定义)。

不过,您可以通过调用reserve 来实现您想要的结果。 IIRC,该标准保证在向量的大小大于其保留容量之前不会进行重新分配。

一般来说,我会小心处理(你很快就会被困……)。最好不要依赖 std::vector&lt;&gt;::reserve 和迭代器持久性,除非你真的必须这样做。

【讨论】:

    【解决方案4】:

    另一种可能性是专门构建的智能指针,而不是存储地址,而是存储向量本身的地址以及您关心的项目的索引。然后它会将它们放在一起并仅在您取消引用它时获取元素的地址,如下所示:

    template <class T>
    class vec_ptr { 
        std::vector<T> &v;
        size_t index;
    public:
        vec_ptr(std::vector<T> &v, size_t index) : v(v), index(index) {}
    
        T &operator*() { return v[index]; }
    };
    

    然后您的int *ptr=&amp;v[0]; 将被替换为:vec_ptr&lt;int&gt; ptr(v,0);

    几点:首先,如果您在创建“指针”和取消引用它之间重新排列向量中的项目,它将不再引用原始元素,而是引用任何元素恰好在指定位置。其次,这不进行范围检查,因此(例如)尝试在仅包含 50 个项目的向量中使用第 100th 个项目将产生未定义的行为。

    【讨论】:

      【解决方案5】:

      如果您不需要连续存储值,可以使用 std::deque 代替 std::vector。它不会重新分配,而是将元素保存在几块内存中。

      【讨论】:

        【解决方案6】:

        正如 James McNellis 和 Alexander Gessler 所说,reserve 是一种预分配内存的好方法。但是,为了完整起见,我想补充一点,要使指针保持有效,所有插入/删除操作都必须从向量的尾部进行,否则项目移位将再次使您的指针无效。

        【讨论】:

        • 失效将取决于指针的使用。如果int *ptr = &amp;v[0]; 旨在表示列表中的特定项目,那么是的,它可能会失效,但如果它旨在表示列表中的第一项,无论它是什么,那么它都不会失效。跨度>
        • 你是对的,当然。我假设用例是引用列表中的特定元素,因为使用可用于在特定索引处检索项目的方法更容易处理相反的情况。
        【解决方案7】:

        根据您的要求和用例,您可能需要查看Boost's Pointer Container Library

        在您的情况下,您可以使用boost::ptr_vector&lt;yourClass&gt;

        【讨论】:

          【解决方案8】:

          我也遇到了这个问题,花了一整天才发现vector的地址变了,保存的地址失效了。对于我的问题,我的解决方案是

          1. 将原始数据保存在向量中并获取相对索引
          2. 向量停止增长后,将索引转换为指针地址

          我找到了以下作品

          1. pointers[i]=indices[i]+(size_t)&vector[0];
          2. pointers[i]=&vector[ (size_t)indices[i] ];

          但是,我还没有弄清楚如何使用 vector.front() 并且我不确定是否应该使用 指针[i]=indices[i]*sizeof(vector)+(size_t)&vector[0] 。我认为参考方式(2)应该是很安全的。

          【讨论】:

            猜你喜欢
            • 2011-08-22
            • 2012-04-07
            • 1970-01-01
            • 2014-12-12
            • 2022-01-04
            • 1970-01-01
            • 2021-10-02
            • 2020-11-22
            • 1970-01-01
            相关资源
            最近更新 更多