6 ms·
My favorite C++ footgun is creating a Vector with some items, taking a reference to, say, &vec[3], then adding another item to the vec, then trying to use the r
by 2bitencryption 5y ago
My favorite C++ footgun is creating a Vector with some items, taking a reference to, say, &vec[3], then adding another item to the vec, then trying to use the reference from the previous step.
If you write C++ it might be obvious what the problem is.
If you don't, this will absolutely ruin your entire day.
The worst part is, 95% of the time, it will probably work without issue.
But eventually, pushing a new item to the Vector will trigger a relocation of the whole vector, which will invalidate your reference and bring down production. Have fun debugging that.
- nicoburns 5y agoYep. I learnt about this when I was learning Rust (which makes this a compile-time error). I was very glad I didn't have to learn this and the 100 other things like that seem to exist in C++ the hard way!
- failwhaleshark 5y agoFootguns might keep you prepared for the zombie apocalypse, but they'll inevitably make it difficult to walk. I'm glad I don't currently use C++ in production, but that may change soon. ):
- krylon 5y agoYou can run into the same problem in C, using malloc/realloc. realloc, in fact, makes for a nasty footgun, too (and remains, of course, available in C++).
- turminal 5y agoYes, but unlike with realloc and a custom dynamic array, C++ references and smart pointers and containers are half-smart and do all sorts of things on their own. Knowing when they will and when they will not be "smart" makes writing C++ really difficult sometimes.
- flohofwoe 5y agoAn important difference is that in C it is usually obvious that a reallocation is happening while in the C++ stdlib, memory management is usually hidden (important to note that this isn't a design fault of the C++ language, but of the C++ stdlib, unfortunately the two are more and more entangled in newer C++ versions). Because of the fact that memory management is hidden in C++, I find claims that C++ is more memory-safe than C quite hilarious. If it works, it works fine, but if anything goes wrong (which is quite easy to achieve) it's much harder to find the actual problem in C++ than in C.
- flyingswift 5y agoWhat is the safe way to achieve the same result?
- Kranar 5y agoThe concept behind this is reference stability, and if you need a collection that has stable references, you must introduce a level of indirection, that is, instead of a vector<T>, you use a vector<unique_ptr<T>> and then you can take references as follows: auto& r = *some_vector[0];
- flyingswift 5y agoThanks! I am just learning C++ for a new gig, and coming from Javascript land, it is a lot to take in :)
- saagarjha 5y agoI would generally suggest avoiding reference stability here (extra heap allocations) and going with the offset-based approach mentioned in the other responses.
- Kranar 5y agoI would generally suggest going for correctness over performance and the solution I provided is correct in the general case. Using an offset is only correct in the special case where objects will not be inserted or removed at an index less than the offset, otherwise you will end up with bugs as the offset becomes invalid upon such operations. Furthermore, depending on the size of T, the performance penalty of the extra heap allocations is amortized over the cost of resizing the vector. That is vector reallocation is significantly faster for a unique_ptr<T> than it is for T when T is large and almost all memory allocators are tuned to allocate objects close together in space when they are allocated close together in time, so you don't lose the cache locality or need to worry about memory fragmentation.
- layoutIfNeeded 5y ago
- duped 5y agoThis, and the entire class of iterator invalidation bugs that force you to memorize which collections are ok for which applications.
- nyanpasu64 5y agoRust takes the approach of flat-out not letting you mutate any collection while you have any references to its contents. It eliminates all dangling pointer bugs... I don't know if it rules out any useful use cases of collections with iterator stability. I think any C++ collection holding unique_ptr is stable (pushing to the collection doesn't invalidate the target of the unique_ptr), and Rust doesn't have an safe ergonomic way to achieve that (perhaps Pin<Box<MagicCell<T>>>, but we don't yet have a MagicCell that makes &mut MagicCell<T> not noalias).
- duped 5y agoThe underlying pointer to a unique_ptr won't be invalidated but the iterator might. Consider if you had a vector of unique_ptrs and inserted into it within a for loop. Depending on the implementation of the iterator this may not be sound (if it's an index, you're probably ok, if it's a pointer, you're screwed). If you wanted to do the same in Rust it would be Vec<Box<T>>. Mutating the collection won't invalidate the pointers.
- nyanpasu64 5y ago> Mutating the collection won't invalidate the pointers. Rust still won't let you mutate a Vec<Box<T>> while you hold a &T or &mut T borrowed from the Vec. Perhaps it would be sound to do so unsafely; Rust would let you mutate a Vec<Rc<T>> while you hold a Rc<T> cloned from the Vec. But I'm not clear on whether Stacked Borrows allows moving the Box while you have a &mut T pointing to the same memory as the Box points to. It definitely does not allow dereferencing the Box. (I heard Stacked Borrows will be updated to make self-referential types sound, and I don't know if it will affect this situation.)
- 5y ago
- pizza234 5y agoInterestingly, AFAIK also Golang suffers from something similar: creating a slice from an array, then performing an operation on the array, that causes resizing - the slice will keep pointing to the old array data.
- kaik 5y agoI kid you not, I spent a full working day debugging this exact same issue (taking a pointer to a vector element, before adding more elements). Very obvious if you understand how C++ and vectors work, yet it took me forever to realize, and it was miserable…
- bentcorner 5y agoI can understand people used to a HLL running into this. It's useful to read the documentation: https://en.cppreference.com/w/cpp/container/vector/insert https://en.cppreference.com/w/cpp/container/vector/insert mentions that references/iterators may be invalidated. I'm not going to pretend that I understand everything on that site (particularly anything about complex templates and things like SFINAE) but often there's comprehensible stuff in there. It can be really helpful.