diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 54bdc1445d..20fb875f50 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -1208,6 +1208,28 @@ struct _Iterator_base0 { static constexpr bool _Unwrap_when_unverified = true; }; +// The machinery below implements iterator debugging (informally "IDL", after the _ITERATOR_DEBUG_LEVEL macro, +// where "L" stands for "level"). It lets a container invalidate ("orphan") its iterators without either side +// needing to know about the other directly, by routing everything through a shared proxy object. Invariants: +// +// * Every container owns a dynamically allocated _Container_proxy at all times, including in its +// default-constructed and moved-from states. This is TRANSITION, ABI: allocating the proxy separately (instead +// of, say, storing it inline) is the major reason many containers' allocator-extended move constructors and +// move assignment operators aren't unconditionally noexcept -- reloading the proxy when allocators compare +// unequal can throw. We intend to revisit this strategy in vNext (see #169). +// * A container and its proxy always point to each other (_Container_base12::_Myproxy and +// _Container_proxy::_Mycont, respectively), regardless of whether IDL is enabled; if a proxy exists, this holds. +// * Every valid iterator holds a non-owning pointer to its parent container's proxy (_Iterator_base12::_Myproxy). +// An orphaned iterator has a null _Myproxy. +// * The proxy's _Myfirstiter, together with each iterator's _Mynextiter, forms an intrusive singly linked list of +// iterators rooted at the proxy. Every valid iterator belonging to a container is reachable through this list; +// there are no valid "free-floating" iterators. +// * Whenever the proxies and the intrusive list are manipulated at runtime, the debug lock (_Lockit(_LOCK_DEBUG)) +// is held. During constant evaluation, we skip the lock entirely and go straight to the unlocked paths (see the +// is_constant_evaluated() checks below), since constant evaluation is inherently single-threaded and _Lockit +// isn't usable there. The only other things we do outside of the lock at runtime are things like iterator +// compatibility checks that compare proxy pointers: those pointers don't change even if the containers are +// being swapped concurrently (only the proxies' data members change, not their addresses). struct _Container_base12; struct _Container_proxy { // store head of iterator chain and back pointer _CONSTEXPR20 _Container_proxy() noexcept = default;