我已经在这个问题上挣扎了大约一个星期了。我不知道问题是什么,或者我是否在错误的地方更新了迭代器。
让我们说明问题。我试图建立一个丢弃系统,在玩家杀死它后随机物品从怪物中掉落。我从下面的容器中取出物品。收到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++;
}
}
我假设我将迭代器增加到错误的位置。我假设我执行了擦除操作,但我确实没有想法。
答案 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
进行比较,则可能导致某些比较被跳过。