【问题标题】:pointer to vector at index Vs iterator指向索引 Vs 迭代器处的向量的指针
【发布时间】:2011-12-18 22:55:18
【问题描述】:

我有一个 vector myvec 在我的代码中用于保存内存中的对象列表。我通过使用

以“正常” C 方式在该向量中保留指向当前对象的指针
Object* pObj = &myvec[index];

如果... myvec 没有增长到足以在 push_back 期间移动,此时 pObj 变得无效,这一切都可以正常工作 - 向量保证数据是顺序的,因此它们不会努力将向量保持在相同的内存位置。

我可以为 myvec 保留足够的空间来防止这种情况,但我不喜欢那个解决方案。

我可以保留所选 myvec 位置的索引,当我需要使用它时直接访问它,但这对我的代码进行了昂贵的修改。

我想知道迭代器是否在重新分配/移动向量时保持其引用完整,如果可以,我可以替换

Object* pObj = &myvec[index];

类似

vector<Object>::iterator = myvec.begin()+index;

这意味着什么?

这可行吗?

保存指向向量位置的指针的标准模式是什么?

干杯

【问题讨论】:

  • 保持索引是“昂贵的”?嗯。
  • 保留迭代器的标准模式是预先保留空间,或使用保留迭代器的容器。无论哪种方式都有妥协。
  • 您在下面说您“几乎将向量用作数组”,这正是您应该做的——但我怀疑您的整个问题是一些糟糕的设计决策的后果。不知何故,这根本不应该是一个问题......只是在不了解上下文的情况下很难判断。
  • 在什么意义上使用索引是一种“昂贵的修改”?你的意思是你有太多的行要改变?还是myvec[i] 的运行时成本大于*pObj 的运行时成本?

标签: c++ pointers stl iterator


【解决方案1】:

迭代器(可能)被任何可以调整向量大小的东西(例如 push_back)失效。

但是,您可以创建自己的迭代器类来存储向量一个索引,这将在调整向量大小的操作中保持稳定:

#include <iterator>
#include <algorithm>
#include <iostream>
#include <vector>

namespace stable {

template <class T, class Dist=ptrdiff_t, class Ptr = T*, class Ref = T&>
class iterator : public std::iterator<std::random_access_iterator_tag, T, Dist, Ptr, Ref>
{
    T &container_;
    size_t index_;
public:
    iterator(T &container, size_t index) : container_(container), index_(index) {}

    iterator operator++() { ++index_; return *this; }
    iterator operator++(int) { iterator temp(*this); ++index_; return temp; }
    iterator operator--() { --index_; return *this; }
    iterator operator--(int) { stable_itertor temp(*this); --index_; return temp; }
    iterator operator+(Dist offset) { return iterator(container_, index_ + offset); }
    iterator operator-(Dist offset) { return iterator(container_, index_ - offset); }

    bool operator!=(iterator const &other) const { return index_ != other.index_; }
    bool operator==(iterator const &other) const { return index_ == other.index_; }
    bool operator<(iterator const &other) const { return index_ < other.index_; }
    bool operator>(iterator const &other) const { return index_ > other.index_; }

    typename T::value_type &operator *() { return container_[index_]; }
    typename T::value_type &operator[](size_t index) { return container_[index_ + index]; }
};

template <class T>
iterator<T> begin(T &container) { return iterator<T>(container, 0); }

template <class T>
iterator<T> end(T &container) { return iterator<T>(container, container.size()); }

}

#ifdef TEST
int main() { 

    std::vector<int> data;

    // add some data to the container:
    for (int i=0; i<10; i++)
        data.push_back(i);

    // get iterators to the beginning/end:
    stable::iterator<std::vector<int> > b = stable::begin(data);
    stable::iterator<std::vector<int> > e = stable::end(data);

    // add enough more data that the container will (probably) be resized:
    for (int i=10; i<10000; i++)
        data.push_back(i);

    // Use the previously-obtained iterators:
    std::copy(b, e, std::ostream_iterator<int>(std::cout, "\n"));

    // These iterators also support most pointer-like operations:
    std::cout << *(b+125) << "\n";
    std::cout << b[150] << "\n";

    return 0;
}
#endif

由于我们不能像普通的迭代器类那样将它作为嵌套类嵌入到容器中,因此需要稍微不同的语法来声明/定义这种类型的对象;而不是通常的std::vector&lt;int&gt;::iterator whatever;,我们必须使用stable::iterator&lt;std::vector&lt;int&gt; &gt; whatever;。同样,要获取容器的开头,我们使用stable::begin(container)

有一点可能有点令人惊讶(至少一开始是这样):当你获得一个stable::end(container)时,那会让你在那个时候结束容器 /em>。如上面的测试代码所示,如​​果您稍后向容器中添加更多项,则您之前获得的迭代器没有调整以反映容器的新端——它保留了它在您获得了它(即,当时容器末端的位置,但不再是了)。

【讨论】:

    【解决方案2】:

    不...使用迭代器会遇到同样的问题。如果执行向量重新分配,则所有迭代器都将失效,使用它们是未定义的行为。

    使用std::vector 抗重新分配的唯一解决方案是使用整数索引。

    使用例如std::list 事情是不同的,但效率妥协也是不同的,所以这真的取决于你需要做什么。

    另一种选择是创建您自己的“智能索引”类,该类存储对向量和索引的引用。这样,您可以只传递一个“指针”(并且您可以为其实现指针语义),但代码不会遭受重新分配风险。

    【讨论】:

    • 问题是列表没有 [] 运算符,这很糟糕,因为我几乎将向量用作数组。
    • @Andre: std::deque 只有在中间插入/删除时才会使迭代器失效。 push_backpop_back 不会失效。他们有[],查找速度更接近vector而不是list
    • @MooingDuck。嗯,这似乎是要走的路。感谢您的帮助;)
    • @MooingDuck:从 23.3.3.4 [deque.modifiers]/p1 开始:在双端队列的任一端插入会使双端队列的所有迭代器无效。 p4 在同一节中指出pop_back()(或擦除最后一个元素)将使结束迭代器无效。
    • @MooingDuck 正如霍华德所指出的,关于迭代器失效的部分是不正确的,但是将“迭代器”替换为“引用(和指针)”,它就变得正确了。
    【解决方案3】:

    不,迭代器在向量增长后失效。

    解决这个问题的方法是保留项目的索引,而不是指向它的指针或迭代器。这是因为即使向量增长,项目也会保持在其索引处,当然前提是您没有在它之前插入任何项目(从而更改其索引)。

    【讨论】:

    • 添加,如果你想保留迭代器,也许看看列表?
    • std::deque,取决于你用它做什么
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-04
    • 2014-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多