It's a good decision to add at least some checks into C++ standard library. But no runtime check can find a bug in code like this:
std::vector<int> v;
v.push_back(123);
auto& n= v.front();
v.push_back(456);
auto n_doubled= n * 2;
A better language is needed in order to prevent such bugs, where such compile-time correctness checks are possible. Some static analyzers are able to detect it in C++, but only in some cases.
It seems to me statically checking this should be possible. The liveness of the result of std::vector::front() should be invalidated and be considered dead after the second invocation to push_back(). Then a static analyser would correctly mark the final line with red squiggles. Of course, compilers would still be happy to compile this, which they really ought not to.
> It seems to me statically checking this should be possible.
Statically checking this specific example (or similarly simple examples) could be possible, sure. I'm not so sure about more complex cases, such as opaque functions (whether because the function is literally opaque or because not enough inlining occurred), stored references (e.g., std::span), unintentional mutation of the underlying data structure, etc.
Thats basically one of the main reason Rust's lifetimes exist - to explicitly encode information about when lifetimes are valid in the type system. C++ doesn't have an equivalent (yet?), so unless you're willing to use global analysis an/or non-standard annotations there's only so much static analysis can do.
In my own experience with “modern-ish C++” (the platform I work with only supports up to C++17 for now), once we started using smart pointers, like unique_ptr and shared_ptr, iterator invalidation has been the primary source of memory safety errors. You have to be so careful any time you have a reference into a container.
In a lot of cases the solution is already sitting there for you in <algorithms> though. One of the more common places we’ve encountered this problem is when someone has a simple task like “delete items from this vector that match some predicate” and then just write a for-loop that does that but doesn’t handle the fact that the iterators can go bad when you modify the vector. The algorithms library has functions in it to handle that, but without a good mental checklist of what’s all in there people will generally just do the simple (and unfortunately wrong) thing.
Not enough that we’ve ever noticed it being at all significant in the profiling we do regularly. The system is an Edge ML processor running about 600 megapixel/sec through a pipeline that does pre- and post-processing on multiple CPUs and does inference on the GPU using TensorRT. In general allocation isn’t anywhere near our bottleneck, it’s all of the image processing work that is.