I am writing a simple custom (special purpose) container and would like to allow for iteration over each element, however, avoid using iterators due to the problem of iterator invalidation.
Instead of providing a pair of iterators (via begin()
and end()
) I was thinking to provide a for_each
method that iterates over the elements and passes them to a functor. The method would increment a counter on entry and decrement it on exit. If the counter is non-null, every time a method that would modify the container (and cause iterator invalidation) is called, it would return early, resulting in a no-op. See the following for a simple example
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cassert>
struct FloatArray {
float *data = nullptr;
size_t count = 0;
size_t niter = 0;
float *begin() { return data; }
float *end() { return data + count; }
template<typename Fn>
void
for_each(Fn &&fn)
{
niter++;
for (size_t k = 0; k < count; k++) {
fn(data[k]);
}
niter--;
}
void
push_back(float f)
{
if (niter) {
/* Return early if the container is being iterated over. */
return;
}
float *new_data = new float[count + 1];
memcpy(new_data, data, sizeof(*data) * count);
if (data) {
delete [] data;
}
new_data[count] = f;
data = new_data;
count++;
}
};
int
main(int argc, char **argv)
{
FloatArray arr;
arr.push_back(1.0f);
arr.push_back(2.0f);
arr.push_back(3.0f);
arr.for_each(
[&arr](float f)
{
if (f == 1.0f) {
arr.push_back(42.0f); // no-op
}
printf("%f\n", f);
}
);
for (float f : arr) {
if (f == 1.0f) {
arr.push_back(42.0f); // Undefined behaviour
}
printf("%f\n", f);
}
arr.push_back(4.0);
return 0;
}
I guess similar behavior could be achieved using iterators pointing back to the container incrementing a counter on construction and decrementing it on destruction, but is think this (and iterators in general) are way more complex to implement (and understand) than the for_each
method.
One concern would be that the "iterator counter" could overflow, but I am not expecting that many nested iterations.
I guess my question is: Would using a for_each
method make more sense for special purpose containers, that do not need iterators for reasons other then iterating over its elements (e.g. specifying a range, or being a reference passed to a container method), since it is more simple to implement and understand (IMO) and (more easily) avoids complexities such as iterator invalidation? Also, are there better implementations then the one described above for this kind of use case?
for_each
method rather than providing iterators is better design for the situation described above. Since it seems easier to handle situations where iterators become invalid. In addition, I feel iterators add a lot of noise in form of boilerplate code for very little use.std::abort()
when a conflict is found (this is a logic error, so it can't be reasonably recovered from). Reminds me of theRefCell
type in Rust, which maintains such counters to ensure that you can't get a non-const reference while any other reference is active.for_each
function, you can break your container for all time.for_each
is a read-only operation. It should not break when called concurrently.