比较第一个向量中的不同向量迭代器和擦除元素

时间:2017-09-11 06:58:29

标签: c++ vector iterator

我已经在这个问题上挣扎了大约一个星期了。我不知道问题是什么,或者我是否在错误的地方更新了迭代器。

让我们说明问题。我试图建立一个丢弃系统,在玩家杀死它后随机物品从怪物中掉落。我从下面的容器中取出物品。收到gear容器中的项目后,我也希望从gear向量中删除收到的项目。即如果"板甲"我被删除了,我想从gear容器中删除它。

我有一个Gears矢量,我可以注册不同的装备,如武器,装甲或配件。在我们的例子中,我只关注 Armor

std::vector<std::unique_ptr<Armor>> gear;

/* This is the simplified version of the vector. 
 I register different elements into my gear vector. Now is only armor focused on.
*/
gear.emplace_back(new Armor("Great pauldron", 25, 100));
gear.emplace_back(new Armor("Holy armor", 3, 18));
gear.emplace_back(new Armor("Dominic's eye", 18, 73));
gear.emplace_back(new Armor("Plate armor", 23, 21));
gear.emplace_back(new Armor("Poor armor", 57, 7));
gear.emplace_back(new Armor("Good shield", 91, 5));
gear.emplace_back(new Armor("Jodin's boots", 18, 66));
gear.emplace_back(new Armor("Ivona's gauntlets", 25, 100));

下一步,我创建一个我收到的类,给定项目数作为矢量迭代器的向量。 (我使用公共函数进行此类操作。)

class Maker {
private:

    template<typename Iter, typename RandomGen>
    Iter randomSelection(Iter begin, Iter end, RandomGen& ran) {
        std::uniform_int_distribution<> dist(0, std::distance(begin, end) - 1);
        std::advance(begin, dist(ran));
        return begin;
    }
public:

    template<typename Iter>
    std::vector<Iter> randomSelection(Iter& begin, Iter& end, int amount) {
        std::vector<Iter> it;
        std::random_device randDev;
        std::mt19937 gen(randDev());
        for (int i = 0; i < amount; i++)
            it.push_back(randomSelection<>(begin, end, gen));
        return it;
    }
};

接下来,我创建了一个vector iterator向量来接收来自gear容器的随机项。

Maker mak;
std::vector<std::vector<std::unique_ptr<Armor>>::iterator>& droppedItems = 
    mak.randomSelection(gear.begin(), gear.end(), 5);

问题来自于我尝试比较两个矢量的装甲名称,如果找到的话;从我们的第一个gear向量中删除它。我几乎总是遇到访问冲突错误。它有时可以删除项目而不会产生任何错误。但每次只有20次尝试。

for (auto& i = gear.begin(); i != gear.end();) {
        for (auto& j = droppedItems.begin(); j != droppedItems.end(); j++) {
            /* This if statement is where I get the access violation error; 0x05.*/
            if (i->get()->getName() == (*j)->get()->getName()) {
                std::cout << std::endl << i->get()->getName() << " has been deleted!\n";
                i = gear.erase(i);
            }
            else
                i++;
        }
    }

我假设我将迭代器增加到错误的位置。我假设我执行了擦除操作,但我确实没有想法。

2 个答案:

答案 0 :(得分:3)

std::vector<T>::erase(iter)使iter之后或之后的迭代器和引用无效(包括end()),但您仍然会尝试在droppedItems容器中使用这些无效的迭代器。< / p>

例如,假设droppedItems还包含指向gears的最后一个元素的迭代器,以及其他一些迭代器。当擦除gears的任何其他元素时,它会使迭代器无效到最后一个元素。因此,当您最终调用gears.erase()将(无效的)迭代器传递给最后一个元素时,它将导致未定义的行为。

std::vector<int> test{1,2,3,4};
auto firstIter = test.begin();
auto secondIter = firstIter + 1;
auto thirdIter = secondIter + 1;
auto fourthIter = thirdIter + 1;
test.erase(secondIter); // Invalidates secondIter, thirdIter and fourthIter
                        // but not firstIter

您可以将std::vector<T>迭代器视为T的指针。如果擦除元素,则后面的元素将在内存中移动,以将矢量元素保持为动态数组。因此,“指向”这些元素的迭代器将不会继续指向正确的元素。

test.erase(secondIter)之前:

address: | 0x0 | 0x1 | 0x2 | 0x3 |
value:   |   1 |   2 |   3 |   4 |

firstIter  = 0x0 // Points to element with value 1
secondIter = 0x1 // Points to element with value 2
thirdIter  = 0x2 // Points to element with value 3
fourthIter = 0x3 // Points to element with value 4

test.erase(secondIter)之后:

address: | 0x0 | 0x1 | 0x2 |
value:   |   1 |   3 |   4 |

firstIter  = 0x0 // Still points to element with value 1
secondIter = 0x1 // no longer points to element with value 2
thirdIter  = 0x2 // no longer points to element with value 3
fourthIter = 0x3 // out of vector bounds

我建议将gears的随机元素设置为nullptr,然后压缩矢量:

template <typename T>
void freeRandomItems(std::vector<std::unique_ptr<T> > & vec,
                     std::size_t amount)
{
    if (amount == 0u)
        return; // Nothing to do

    // Setup RNG:
    std::random_device randDev;
    std::mt19937 gen(randDev());

    if (amount == 1u) { // Remove only one random element:
        vec.erase(randomSelection(vec.begin(), vec.end(), gen));
        return;
    }

    // Deallocate amount pointed elements in vector, reset pointers:
    do {
        randomSelection<>(vec.begin(), vec.end(), gen)->reset();
    } while (--amount);

    // Remove all nullptr elements from vec:
    vec.erase(
            std::remove_if(
                    vec.begin(),
                    vec.end(),
                    [](std::unique_ptr<T> const & v) noexcept { return !v; }),
            vec.end());
}

答案 1 :(得分:1)

根据您的情况,以不同的方式做事可以避免这种重新验证业务。您可以在嵌套循环中为齿轮添加临时矢量中不匹配的元素,然后将该矢量移动到原始齿轮矢量中。像这样的东西?

更新:已发布的代码存在轻微错误。固定相同。此代码现在可以在我的机器上运行并提供正确的结果。

decltype(gear) newGearTmp;
//Reserve size for remaining elements in newGearTmp vector
newGearTmp.reserve(gear.size() - droppedItems.size());
for (auto& i = gear.begin(); i != gear.end(); i++) {
    bool lbFound = false;
    for (auto& j = droppedItems.begin(); j != droppedItems.end() && !lbFound; j++) {
        if (i->get()->getName() == (*j)->get()->getName()) {
            std::cout << std::endl << i->get()->getName() << " has been deleted!\n";
            //i = gear.erase(i); //No need for this
            lbFound = true;
        }
    }
    !lbFound ? newGearTmp.push_back(std::move(*i)) : 0;
}
gear = std::move(newGearTmp); //gear now has new armor, unwanted elements are deleted

与当前的实现相比,这不应该是性能更高的,这也涉及在擦除后交换元素的开销。

此外,在代码的这一部分中,您应该按值而不是引用获取已删除项目的向量。像这样。

Maker mak;
std::vector<std::vector<std::unique_ptr<Armor>>::iterator> droppedItems = 
    mak.randomSelection(gear.begin(), gear.end(), 5);

通过引用获取将导致droppedItems指向已删除的临时向量,该向量对于函数mak.randomSelection()的范围是活动的。

还有一件事。在原始提交的代码中,i++似乎有些不合适。如果i递增而不与所有j进行比较,则可能导致某些比较被跳过。