【问题标题】:How to maintain reference/pointer/link to vector element after change to vector?更改为向量后如何维护指向向量元素的引用/指针/链接?
【发布时间】:2016-08-17 16:36:13
【问题描述】:

我有一个自定义类的std::vector(为简单起见,在示例中使用int)。我想保留向量成员的引用/指针/链接/其他。但是,向量经常会删除和添加元素。

为了说明我的观点,在下面的示例中,我采用了指向向量第二个元素的引用或指针。我使用引用/指针来增加所选元素的值。然后我擦除第一个元素,并使用 ref/pointer 再次递增。

参考例子:

std::vector<int> intVect = {1,1,1};
int& refI = intVect.at(1);
refI++;
intVect.erase(intVect.begin());
refI++;

智能指针示例:

std::vector<int> intVect2 = {1,1,1};
std::shared_ptr<int> ptrI = std::make_shared<int>(intVect2.at(1)) ;
*ptrI = *ptrI +1;
intVect2.erase(intVect2.begin());
*ptrI = *ptrI +1;

我希望最终引用元素的值为 3,最终向量由 {3,1} 组成。但是,在参考示例中,最终向量为{2,2},而在指针示例中,最终向量为{1,1}

了解指针本质上是一个内存地址,我可以理解为什么这种方法可能不可行,但如果它以某种方式可行,请告诉我。

那么更重要的问题是,可以使用哪些替代方法或结构来允许对该元素的某种形式的引用/指针/链接/其他(无论是值还是对象) ) 在向包含它的向量(或其他结构)添加成员或从中删除成员后是否可行?

额外积分:

我实际使用的对象具有position 属性。我有第二个结构需要跟踪对象,以便快速查找哪些对象在哪些位置。我目前正在使用网格(向量向量)来表示可能的位置,每个位置都将索引保存到当前位于该位置的对象的对象向量中。但是,当从向量中删除一个对象时(这种情况非常频繁,每次迭代最多数百次),我目前的手段是循环遍历每个网格位置并减少任何大于删除索引的索引,这既慢又笨拙.非常感谢您在上下文中对这个问题的其他想法,但我的关键问题涉及上述示例。

【问题讨论】:

  • 第二个例子不是UB。 shared_ptr 完全独立于 vector
  • @underscore_d 我的感觉是vector 不是实现此类事情的最佳方式(非常不稳定的容器)。可能是map?即使使用ints 作为键,使用映射它们将始终保持有效,除非相同的键被擦除
  • @mvidelgauz 确实,或者list,这似乎更好——只会使erase()d 元素的迭代器无效。
  • @mvidelgauz 如果可以更改容器类型,std::list 将是提供所需行为的最简单的方法。
  • @aschepler 是的,容器类型肯定可以改变,事实上,这是我的问题的症结所在,询问哪些替代方法(因为我的方法在当前状态下显然无效)会达到理想效果。

标签: c++ pointers vector data-structures reference


【解决方案1】:

一种可能的选择是让向量存储std::shared_ptr 对象,并发出std::weak_ptrstd::shared_ptr 对象来引用相关对象。

std::vector<std::shared_ptr<int>> ints;
for(size_t i = 0; i < 10000; i++) {
    ints.emplace_back(std::make_shared<int>(int(i)));
}
std::weak_ptr<int> my_important_int = ints[6000];
{
    auto lock = my_important_int.lock();
    if(lock) std::cout << *lock << std::endl;
    else std::cout << "index 6000 expired." << std::endl;
}

auto erase_it = std:remove_if(ints.begin(), ints.end(), [](auto & i) {return (*i) > 5000 && ((*i) % 4) != 0;});
ints.erase(erase_it, ints.end());

{
    auto lock = my_important_int.lock();
    if(lock) std::cout << *lock << std::endl;
    else std::cout << "index 6000 expired." << std::endl;
}

ints.erase(ints.begin(), ints.end());

{
    auto lock = my_important_int.lock();
    if(lock) std::cout << *lock << std::endl;
    else std::cout << "index 6000 expired." << std::endl;
}

应该打印出来:

6000
6000
index 6000 expired.

【讨论】:

    【解决方案2】:

    存储键/值对的容器可能适合您。例如,std::mapstd::unordered_map

    使用这些容器时,您可以通过存储密钥来保持对所需对象的引用。如果要修改所述对象,只需使用键在容器中查找它。现在,您可以根据需要添加/删除其他对象,而不会影响相关对象(假设添加/删除的对象具有唯一键)。

    【讨论】:

    • std::list 可能会更好,因为使用 map 意味着添加对密钥的要求:如果这些在 OP 的情况下没有意义,则必须人为/任意将它们移植到程序的设计。相比之下,使用list,他们可以只保留对元素的安全迭代器/指针/引用,只有通过erase()ing 特定元素才会失效。顺便说一句,他们也得到了与maps 相同的保证,但再次增加了对可能没有任何用处的密钥的要求。
    【解决方案3】:

    如果有办法让您继续使用向量并改变管理对象的方式,那么您将不会获得比现在更高的性能。

    否则,您可以使用 稳定 向量 (here's the boost version)。它本质上是一个指针向量,赋予它迭代器和引用稳定性。这意味着迭代器(指针)和对元素的引用不会因除删除元素本身之外的任何操作而失效。

    当然,这样做有一些很大的缺点,主要是在性能方面。两个主要的性能问题是每次要访问元素时都要通过指针,以及元素不是连续存储的事实(这当然会影响迭代速度)。

    然而,它也比其他重指针的数据类型(列表、集合、映射)具有优势。主要是,它在恒定时间内执行查找和推回,即使它比法线向量慢。

    再说一次,如果您真的需要性能,您可能希望保留您的矢量并围绕它重新考虑您的设计。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-15
      • 1970-01-01
      • 2011-03-12
      • 1970-01-01
      • 1970-01-01
      • 2013-11-21
      • 2011-08-04
      • 1970-01-01
      相关资源
      最近更新 更多