Разработка итератора для исключения при разыменовании

Мне нужно найти способ проверить мою обработку исключений в функции, которую я написал под названием hpx::parallel::copy, Другие функции в библиотеке, такие как hpx::parallel::transform легко проверить, так как можно передать предикат, в котором выдается исключение, но copy не принимает предикат

Я думаю, что моим лучшим решением было бы использовать итератор, который как-то добавляет разыменование, хотя я не совсем уверен, как это сделать …… любые другие предложения по решению этой проблемы также приветствуются. Вот пример кода моей проблемы

//transform.cpp , testing exceptions
bool caught_exception = false;
try {
base_iterator outiter = hpx::parallel::transform(policy,
iterator(boost::begin(c)), iterator(boost::end(c)), boost::begin(d),
[](std::size_t v) {    //easy, predicate can be passed
throw std::runtime_error("test");
return v;
});

HPX_TEST(false);
}
//catching exceptions etc...

//copy.cpp, testing exceptions
bool caught_exception = false;
try {
base_iterator outiter = hpx::parallel::copy(policy,
iterator(boost::begin(c)), iterator(boost::end(c)), boost::begin(d)); //no predicate... how can I throw?
HPX_TEST(false);
}
//catching exceptions etc..

чтобы быть более конкретным, я хочу иметь возможность изменить то, что именно я хочу throw для того, чтобы протестировать несколько сценариев, это просто означает, что я не могу использовать реализацию, которая выбрасывает из диапазона или другие исключения, которые я не могу контролировать, мне нужно выбросить определенные исключения.

2

Решение

Подход, отличный от создания ваших собственных итераторов, заключается в создании декоратор класс уже существующего класса итератора. Примером игрушки может быть:

#include<functional>

/**
* @brief Decorates an iterator to permit code injection before dereferencing
*/
template<class T>
struct IteratorDecorator : public T {

template<class V>
IteratorDecorator(T iterator, V f) : T(iterator) , m_callback(f) {}

typename T::value_type & operator*() {
m_callback();
return T::operator*();
}

private:
std::function<void()> m_callback;
};

/**
* @brief Convenience function just for type deduction
*/
template<class T, class V>
IteratorDecorator<T> decorate(T iterator, V v) {
return IteratorDecorator<T>(iterator,v);
}

Это может быть использовано в клиентском коде следующим образом:

int main()
{
vector<int> ivec {1, 3, 5, 6};

try {
for_each(ivec.begin(),ivec.end(),[](int& x){ cout << x << endl; } );
for_each(decorate(ivec.begin(), [](){ cout << "decorated : "; }),
decorate(ivec.end()  , [](){}),
[](int& x){ cout << x << endl; });
for_each(decorate(ivec.begin(), [](){ throw runtime_error("This one throws"); }),
decorate(ivec.end()  , [](){}),
[](int& x){ cout << x << endl; } );
} catch( exception& e) {
cout << e.what() << endl;
}

return 0;
}

Если вы хотите поэкспериментировать с кодом, вы можете найти рабочую версию Вот.

1

Другие решения

Самый простой подход — создать ваши итераторы с обратной ссылкой на контейнер, по которому они итерируются. Всякий раз, когда вы увеличиваете контейнер end()или уменьшить его begin()или когда вы разыменовываете что-либо вне диапазона контейнера, вы бросаете исключение. Поскольку итератор имеет ссылку на контейнер, у вас есть вся эта информация. Накладные расходы — это простая ссылка (или указатель) на итератор и небольшое количество логики в operator--, operator++ а также operator*,

Microsoft использует такой подход в своих Проверенные итераторы которую вы можете включить при использовании их стандартной библиотеки. Образец реализация дан в SafeSTL. Например. их vector<T> выглядит примерно так:

template<class T>
class vector
{
public:
class iterator
{
public:
// regular iterator interface
private:
std::vector<T>* owner; // used by implementation to do checking
};

// rest of vector<T> interface
};
1

Или вы могли бы сделать простейшую возможную вещь и написать тип значения, для которого генерируется оператор присваивания копии (и / или переместить оператор присваивания, конструкторы копирования и перемещения, …).

Так как вы заполняете контейнер в первую очередь, вы можете даже выбрать, какие значения выкинуть, если хотите. Там гораздо меньше, чем написание итератора.

NB. Я предполагаю, что вы хотите проверить свой алгоритм, вызвав исключение. Я думаю, что предложение TemplateRex направлено больше на итераторов, которые ловят случайное неправильное использование во время выполнения. Не стесняйтесь, чтобы уточнить.


Пример реализации:

Самый простой возможный тип значения — он не имеет фактического значения и всегда выбрасывает копию или перемещение:

struct TrivialThrowOnCopy {
TrivialThrowOnCopy() = default;
TrivialThrowOnCopy(TrivialThrowOnCopy const &) {
throw std::runtime_error("copy");
}
TrivialThrowOnCopy(TrivialThrowOnCopy&&) {
throw std::runtime_error("move");
}
};

Или у вас может быть один, где вы будете явно указывать каждому экземпляру, бросать или нет:

struct ConfigurableThrowOnCopy {
bool should_throw_;
explicit ConfigurableThrowOnCopy(bool b = false) : should_throw_(b) {}
ConfigurableThrowOnCopy(ConfigurableThrowOnCopy const &other)
: should_throw_(other.should_throw_) {
if (should_throw_) throw std::runtime_error("copy");
}
ConfigurableThrowOnCopy(ConfigurableThrowOnCopy &&other)
: should_throw_(other.should_throw_) {
if (should_throw_) throw std::runtime_error("move");
}
};

или тот, где каждая _n_th копия выбрасывает:

struct CountingThrowOnCopy {
static unsigned counter;
// set CountingThrowOnCopy::counter = 5 to make the 5th copy throw
CountingThrowOnCopy() = default;
CountingThrowOnCopy(ConfigurableThrowOnCopy const &) {
if (!--counter) throw std::runtime_error("copy");
}
CountingThrowOnCopy(ConfigurableThrowOnCopy&&) {
if (!--counter) throw std::runtime_error("move");
}
};

или любой из вышеперечисленных, но с фактическим значением:

template <typename T>
struct ConfigurableThrowOnCopyT {
T value_;
bool should_throw_;
explicit ConfigurableThrowOnCopyT(T const &t, bool b = false)
: value_(t), should_throw_(b) {}
ConfigurableThrowOnCopyT(ConfigurableThrowOnCopyT const &other)
: value_(other.value_), should_throw_(other.should_throw_) {
if (should_throw_) throw std::runtime_error("copy");
}
ConfigurableThrowOnCopyT(ConfigurableThrowOnCopyT &&other)
: value(std::move(other.value_)), should_throw_(other.should_throw_) {
if (should_throw_) throw std::runtime_error("move");
}
T& operator() { return value_; }
T const& operator() const { return value_; }
};
1
По вопросам рекламы [email protected]