C++ / a working model

BOOK / SOURCE & READING RECORD

The C++ Standard Library: A Tutorial and Reference

Nicolai M. Josuttis

2nd edition, first printing March 2012, ISBN 9780321623218;另读third printing July2013样章及2012补充章

READING EVIDENCE / Full text read

The C++ Standard Library: A Tutorial and Reference

The complete body text of the English 2nd edition chapters 1–19 (pp. 1–1030) has been read paragraph by paragraph, plus the bibliography and supplementary chapters S.1–S.3 (pp. 1103–1161); main text L635–44987 and supplementary body L48029–50769 have been read. Subsequently, the 51 PDF pages that contain figure captions and 6 probability-formula/context pages were checked directly, covering all located figures and the two-dimensional formulas in §17.1.5. “full” means the body text and these substantial figures have been read; it does not mean every PDF page was collated. The index was only sampled for navigation, not read entry by entry.

Edition, actual reading range, and original sources →

Sources

chrono units: do not turn 1500 milliseconds into 1 millisecond

§5.6–§5.7, pp. 140–160; Supplementary Chapter S.1–S.3, pp. 1103–1161

The material already read shows that the value of the standard library is not merely reducing the amount of code, but carrying constraints with the interface: duration records units, bitset records bit width, and allocator distinguishes obtaining storage from constructing objects. Time conversions most easily lose meaning when count() is taken out, so the exercise does not measure real clock speed; it only verifies the correspondence of the same budget under different representations and the truncated remainder. The valarray subset in the supplementary chapters is a proxy object and must not be mistaken for an independent container; its allocator chapter contains both C++98 and C++11 models and cannot be transplanted to C++20 without distinction. A modern reading should keep the principle that “types express units and responsibilities,” look up current interfaces separately, and not treat old-version signature tables as forever-valid specifications.

The container–algorithm split leaves capacity and boundary responsibility to the caller

§6.3–6.7, pp. 188–223; §6.11–6.13, pp. 244–251

The STL separates storage structure from processing strategy; iterators are the protocol between the two. That lets algorithms process subranges, arrays, and input streams, and it also means algorithms do not know how a container grows: copy writing through an ordinary iterator overwrites existing elements; only back_inserter turns assignment into append. reserve does not create overwritable elements. remove returns a new logical end and does not delete objects for the container. Keys of associative containers cannot be changed through ordinary mutating algorithms, or else comparison or hash invariants lose their foundation. Generic does not mean ignoring structure entirely: list’s member remove can relink nodes, while generic remove moves values. Modern ranges make boundary expression more direct, and C++20 erase_if can shorten common erasures, but these conveniences do not eliminate iterator invalidation and borrow lifetime.

Function objects may have configuration, but a predicate must not depend on accidental call history

§3.1.10; §6.8–6.10, pp. 28–32, 224–243

The most important property of a predicate is not whether it is written as a function, a class, or a lambda, but that the same input yields a consistent judgment over the required range. Capturing a threshold by value to form a stable configuration is entirely reasonable; using an internal counter to decide “which nth element counts as a match” can be broken when an algorithm copies the predicate or chooses a different traversal strategy. for_each returning the function object can carry specific accumulation semantics, but that guarantee must not be generalized to every algorithm. Nested bind in the book demonstrates composition; today a lambda usually makes arguments and lifetime easier to see. Without evidence, “function objects are often easier to inline” must not be stated as absolutely faster. Restrictions such as captureless lambdas not being default-constructible belong to C++11 and have changed in C++20; closures with reference captures always need extra proof that the referred-to objects are still alive.

Invalidation rules must separately describe object addresses and traversal positions

§7.1.2, §7.2.1, §7.3.2, §7.4.2, §7.9.2–7.9.3, §8.4, §8.9.2, pp. 258–263, 275–276, 286, 363–373, 407–408, 428–429

After a container changes structure, iterators, references, and pointers need not all become invalid at once. An unordered container’s rehash changes the traversal structure but preserves element references; insertion at the ends of a deque also cannot be summarized as “all handles remain stable.” Even without reallocation, vector may still invalidate old handles at and after the insertion point. swap usually lets handles follow elements into the other container, whereas array swaps values at fixed positions. When choosing a container, first write down which relationships must be preserved, then compare complexity and locality. Hash tables also require equivalent keys to have the same hash; failing that is a contract error, not merely worse performance. C++17 node handles allow extracting, changing the key, and reinserting, but that does not mean you may bypass the container to modify an ordered key in place.

Streams, characters, and tasks each need to keep their own state boundaries

§15.4, §15.11–15.14; §16.1–16.4; §18.1–18.7, pp. 758–766, 812–848, 850–906, 945–1022

An input stream retains error bits, so clear only clears state and does not consume the character that caused failure; custom parsing should first read into a temporary and commit the object only after validation succeeds. streambuf is responsible for character transfer, ostream for formatting and state; optimizations that go directly through the buffer also bypass some of that semantics. locale is again different from char_traits: the former composes run-time cultural rules, the latter defines character-type operations, and neither is a general Unicode processor. Concurrent tasks likewise cannot be viewed only as values: future also carries completion and exceptions, async may by default execute lazily, locks protect shared invariants, and condition variables wait on predicates. Collapsing these layers into “one convenient API” loses recovery and lifetime information. C++20 has more tools, but it has not cancelled these interface responsibilities.

Related fundamentals

Allocator and pmr: choosing a resource and its lifetimeType traits and if constexpr compile-time branchingtypedef and using: A Type Alias Is Not a New Type

Original practice →