I am working with an older C (compiling with clang++) codebase that used some global variables to store the heads of linked lists, with many points throughout the code that iterate over those lists.
struct global_st *global_list; // awful global
// ... somewhere down in the code
for (struct global_st *it = global_list; it; it = it->next)
operation(it);
At some point, some geniuses figured out that operation() could result in the removal of items from the list, especially (but not limited to) current object of iteration. So some loops look more like this:
for (struct global_st *it = global_list, *next_it; it; it = next_it) {
next_it = it->next;
operation_that_could_remove_object(it);
}
However, many of the less safe loops use the other method, especially if using a relatively "safe" operation (this codebase suffers from almost complete lack of const). I have located a few bugs with this, which are masked by the use of object pooling for the allocated structs that never have their next pointers cleared.
I have been tasked with changing this linked list to a std::vector or std::list. From this question, which is 7 years old but I assume is still true, I can't check if the iterator is still valid for dereferencing. Assuming that the current object of iteration is the only element that can be removed by an operation in the loop, would this loop be considered safe?
for (auto it = global_list.begin(), next_it; it != global_list.end(); it = next_it) {
next_it = it;
next_it++;
operation_that_could_remove_object(*it);
}
If so, is there a simpler way (or a safer data structure) that would help prevent programmer error? I would like to use the ranged-for semantics, but that doesn't seem possible when the iterator could be removed.
My alternative option would be to utilize the object pool and set an 'is_valid' flag, and then defer removal to a later time. However, I don't particularly want to require every loop to check if the object is still valid.
EDIT: To clarify, the container is filled with unique pointers to objects, and the possible removal (by operation(Obj *obj)) will be by matching the pointer to a member of the container. Since they're unique, are there semantics that could be applied to iteration/potential removal from a std::set that would work?