Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions stl/inc/xmemory
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down