В итоге я выбрал собственный итератор, который, если владелец std :: vector изменяет размеры, пока итератор все еще находится в области видимости, будет регистрировать ошибку или прерывание (предоставляя мне стековый след программы).Этот пример немного запутан, но я попытался максимально упростить его и удалил неиспользуемые функциональные возможности итератора.
Эта система выявила около 50 ошибок такого рода.Некоторые могут повторяться.Однако Valgrind и ElecricFence в этот момент оказались чистыми, что разочаровывает (в общей сложности они отметили около 10, которые я уже исправил с начала очистки кода).
В этом примере я использую clear() , который Valgrind помечает как ошибку.Однако в реальной кодовой базе это стирает произвольный доступ (т.е. vec.erase (vec.begin () + 9) ), который мне нужно проверить, и, к сожалению, Valgrind пропускает немало.
main.cpp
#include "sstd_vector.h"
#include <iostream>
#include <string>
#include <memory>
class Bomb;
sstd::vector<std::shared_ptr<Bomb> > bombs;
class Bomb
{
std::string name;
public:
Bomb(std::string name)
{
this->name = name;
}
void touch()
{
if(rand() % 100 > 30)
{
/* Simulate everything being exploded! */
bombs.clear(); // Causes an ABORT
std::cout << "Crickey! The bomb was set off by " << name << std::endl;
}
}
};
int main()
{
bombs.push_back(std::make_shared<Bomb>("Freddy"));
bombs.push_back(std::make_shared<Bomb>("Charlie"));
bombs.push_back(std::make_shared<Bomb>("Teddy"));
bombs.push_back(std::make_shared<Bomb>("Trudy"));
/* The key part is the lifetime of the iterator. If the vector
* changes during the lifetime of the iterator, even if it did
* not reallocate, an error will be logged */
for(sstd::vector<std::shared_ptr<Bomb> >::iterator it = bombs.begin(); it != bombs.end(); it++)
{
it->get()->touch();
}
return 0;
}
sstd_vector.h
#include <vector>
#include <stdlib.h>
namespace sstd
{
template <typename T>
class vector
{
std::vector<T> data;
size_t refs;
void check_valid()
{
if(refs > 0)
{
/* Report an error or abort */
abort();
}
}
public:
vector() : refs(0) { }
~vector()
{
check_valid();
}
vector& operator=(vector const& other)
{
check_valid();
data = other.data;
return *this;
}
void push_back(T val)
{
check_valid();
data.push_back(val);
}
void clear()
{
check_valid();
data.clear();
}
class iterator
{
friend class vector;
typename std::vector<T>::iterator it;
vector<T>* parent;
iterator() { }
iterator& operator=(iterator const&) { abort(); }
public:
iterator(iterator const& other)
{
it = other.it;
parent = other.parent;
parent->refs++;
}
~iterator()
{
parent->refs--;
}
bool operator !=(iterator const& other)
{
if(it != other.it) return true;
if(parent != other.parent) return true;
return false;
}
iterator operator ++(int val)
{
iterator rtn = *this;
it ++;
return rtn;
}
T* operator ->()
{
return &(*it);
}
T& operator *()
{
return *it;
}
};
iterator begin()
{
iterator rtn;
rtn.it = data.begin();
rtn.parent = this;
refs++;
return rtn;
}
iterator end()
{
iterator rtn;
rtn.it = data.end();
rtn.parent = this;
refs++;
return rtn;
}
};
}
Недостатками этой системы является то, что я должен использовать итератор, а не .at (idx) или [idx] .Я лично не против этого.Я все еще могу использовать .begin () + idx , если необходим произвольный доступ.
Это немного медленнее (хотя ничто по сравнению с Valgrind).Когда я закончу, я могу выполнить поиск / замену sstd :: vector на std :: vector , и не должно быть снижения производительности.