在C++中,持有迭代器的指针是否安全?

8

首先提出问题,接着解释动机,最后给出一个编译并执行正常的示例代码。

问题

如果我能确保迭代器在使用期间不会失效,那么持有迭代器的指针(例如指向list<int>::iterator的指针)是否安全?

动机

我有多个容器,需要直接交叉引用一个容器中的项与另一个容器中对应的项等等。一个容器中的项并不总是在另一个容器中都有对应项。

因此我的想法是,在容器#1中存储指向容器#2中元素的迭代器的指针,以此类推。为什么这么做呢?因为一旦我拥有了一个迭代器,我不仅可以访问容器#2中的元素,而且如果需要,还可以删除容器#2中的元素等等。

如果容器#2中有相应的元素,我将在容器#1的元素中存储指向该迭代器的指针。否则,该指针将被设置为NULL。现在我可以快速检查指向迭代器的指针是否为NULL,如果是,则没有相应的容器#2中的元素,如果非NULL,则可以继续访问它。

那么,以这种方式存储迭代器的指针是否安全呢?

示例代码

#include <iostream>
#include <list>

using namespace std;

typedef list<int> MyContainer;
typedef MyContainer::iterator MyIterator;
typdef MyIterator * PMyIterator;

void useIter(PMyIterator pIter)
{
    if (pIter == NULL)
    {
    cout << "NULL" << endl;
    }
    else
    {
    cout << "Value: " << *(*pIter) << endl;
    }
}

int main()
{
    MyContainer myList;

    myList.push_back(1);
    myList.push_back(2);

    PMyIterator pIter = NULL;

    // Verify for NULL
    useIter(pIter);

    // Get an iterator
    MyIterator it = myList.begin();

    // Get a pointer to the iterator
    pIter = & it;

    // Use the pointer
    useIter (pIter);
}

2
为什么不直接保存迭代器,使用 end 迭代器表示 NULL 呢? - user1773602
另外还有 Boost.Optional,它是一种通用的方式,可以表示“要么是一个值,要么是一个没有值的特殊情况”。因为所有的东西都在一个地方处理,所以你不必考虑指针的引用对象的生命周期。 - Steve Jessop
保存指针(希望如此)给我两个好处:1.轻量级 2.不需要担心end()本身无效。现在我承认我不确定这些问题是否有效。 - kman
谢谢@SteveJessop,今天学到了新东西。我尝试使用optional<MyIterator>的Boost Optional,效果很好。 - kman
5个回答

13

迭代器通常被按值处理。例如,begin()end()将返回类型为iterator(对于给定的迭代器类型)的实例,而不是iterator&,因此它们每次返回一个值的副本。

当然,您可以取这个副本的地址,但不能期望新的begin()end()调用会返回具有相同地址的对象,并且该地址仅在您自己持有迭代器对象时有效。

std::vector<int> x { 1, 2, 3 };

// This is fine:
auto it = x.begin();
auto* pi = &it;

// This is not (dangling pointer):
auto* pi2 = &x.begin();

维护指向迭代器的指针通常没有意义:迭代器已经是轻量级的数据句柄。进一步的间接通常是设计不良的信号。特别是在您的示例中,指针毫无意义。只需传递普通迭代器即可。


谢谢,讲解得非常清楚,我已经理解了。由于我没有所需的“声望”,因此无法点赞。 - kman

2
迭代器的问题在于,容器上有很多操作会使它们失效(具体取决于所涉及的容器)。当你持有一个属于另一个类的容器的迭代器时,你永远不知道这样的操作何时发生,也没有简单的方法可以找出迭代器现在是否无效。
此外,直接删除属于另一个类的容器中的元素,违反了封装原则。当你想要删除另一个类的数据时,最好调用该类的公共方法来删除数据。

你所说的话与指针没有直接关系,如果是真的,那么迭代器通常会变得毫无用处 - 幸运的是这并不是事实。 - Konrad Rudolph

1

是的,只要您能确保迭代器不会失效并且不会超出范围,就是安全的。


1

听起来很可怕。迭代器是一个对象,如果它离开作用域,你的指针就无效了。如果你在容器#2中删除一个对象,所有迭代器可能会变得无效(取决于容器),因此你的指针变得无用。

为什么不直接存储迭代器本身呢?对于那些不引用任何东西的容器#1中的元素,可以存储container2.end()。只要迭代器没有失效,这样做是可以的。如果它们失效了,你需要重新生成映射。


我的一个担忧是避免担心end()迭代器的改变或失效。我不确定这是否是一个有效的担忧。 - kman
如果你担心这个问题,那么你必须担心所有迭代器都会失效,因此你不能存储它们(或指向它们的指针)。end() 迭代器应该至少在与同一容器的所有其他迭代器一样长的时间内有效。 - Tannin
你说得对,我同意。虽然避免过于苛求,但我一直在寻找一个“常量”哨兵值来表示end(),以便始终提供一个不变的目标进行检查。然而,阅读了关于此的资料(例如[https://dev59.com/m2w15IYBdhLWcg3wuuCn]),似乎在某些情况下,被操作的项和end迭代器可能会失效。被操作的项没问题,因为我知道我在操作什么,但是end()对我来说似乎有点混淆。 - kman

1

是的,可以像其他类型一样使用指向迭代器的指针进行操作,但在您的示例中并不需要,因为您可以简单地将原始迭代器作为引用传递。

通常不建议存储迭代器,因为随着您修改容器,迭代器可能会变得无效。最好存储容器,并根据需要创建迭代器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接