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

Compare different vector iterators and erase element from the first vector

本文关键字:迭代器 擦除 元素 第一个 向量 比较      更新时间:2023-10-16

我一直在为这个问题而苦苦挣扎大约一周。我不知道问题是什么,或者我在错误的地方更新迭代器。

让我们指出。我正在尝试制作一个下降系统,在玩家杀死玩家之后,从怪物中删除了随机物品。我从下面的容器中获取物品。从gear容器中收到项目后,我也想从gear向量删除收到的项目。IE。如果掉落"板装甲",我想从gear容器中删除它。

我有一个齿轮矢量,我在其中注册了不同的齿轮,例如武器,装甲或配件。在我们的情况下,我只专注于 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;
    }
};

接下来,我制作一个向量迭代器的向量,以从gear容器中接收随机项目。

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

问题到了,我试图比较两个向量和如果发现的盔甲名称;将其从我们的第一个gear矢量中删除。我几乎总是遇到访问违规错误。有时它可以删除项目而不会产生任何错误。但是每次一次即可尝试一次。

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++;
        }
    }

我假设我会在错误的位置增加迭代器。我认为我的擦除操作很好,但我实际上是没有想法的。

std::vector<T>::erase(iter)iter(包括 end())时或之后,迭代器和引用无效,但是您仍然尝试使用这些无效的迭代器,此后 droppedItems containe。

例如,如果说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());
}

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

更新:在发布的代码中存在一个小错误。修复了相同的。该代码现在正在我的机器上工作并给出正确的结果。

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的增加而无需与所有js。

进行比较,因此可能会跳过一些比较。