CppCon 2017: Allocators: The Good Parts - A Simplified Approach to Custom Memory Management in C++
0:27 Allocator Criticism: The speaker highlights the negative perception of allocators within the C++ community, citing prominent figures like Scott Meyers who have criticized their complexity and limited usability.
1:24 Thesis: The talk argues that with guidance and by leveraging C++17 features, allocators can be made both useful and user-friendly.
2:17 Analogy: The speaker compares allocators to choosing a payment method on a website like Amazon. While the vendor doesn't care how you pay, the user might have preferences due to rewards points, business expenses, etc. Similarly, allocators allow users to customize memory allocation based on their specific needs.
3:16 Reasons for Using Allocators: The speaker lists several reasons why users might want control over memory allocation, including specialized allocation patterns, locality optimization, utilizing special-purpose memory (persistent memory, high-bandwidth memory), thread locality, and statistics gathering.
4:43 Speaker's Role: Pablo Halpern introduces himself as being involved in the standardization of allocators in C++, particularly the improvements introduced in C++11 and C++17.
5:39 Historical Complexity: The speaker discusses the historical reasons for the complexity surrounding allocators, including their initial design to address near and far pointers, attempts at generalization, and the need for backward compatibility with older standards.
7:30 Allocator Traits: The introduction of allocator traits in C++11 added a layer of template indirection to support both old and new allocator models, further increasing complexity.
9:01 Copy Propagation Issues: The speaker illustrates the complex and often counterintuitive behavior of allocator propagation during object copying in C++98/03, highlighting the need for a simpler model.
12:46 Scoped Allocator Model: The scoped allocator model is introduced as the ideal scenario where the same allocator is used for a container and all its elements, ensuring consistent memory management.
13:23 Scoped Allocator Adaptor (C++11): This adaptor was introduced in C++11 to enable the scoped allocator model by wrapping existing allocators.
13:47 Polymorphic Allocator (C++17): C++17 introduced the polymorphic allocator, simplifying the scoped allocator model further.
14:00 Polymorphic Memory Resources (C++17): Polymorphic memory resources are introduced as a new base class (pmr::memory_resource) for creating custom memory allocation mechanisms. These resources work with raw bytes, simplifying allocation.
14:15 Polymorphic Allocator as a Wrapper: The pmr::polymorphic_allocator acts as a wrapper around a pointer to a pmr::memory_resource.
14:46 pmr::vector: The pmr namespace includes standard containers like pmr::vector, which use the pmr::polymorphic_allocator by default.
15:28 Key Functions of pmr::memory_resource: The core functions of pmr::memory_resource are allocate, deallocate, and is_equal. They are not virtual themselves but delegate to protected virtual functions for implementation in derived classes.
17:06 Default Memory Resource: The standard provides a default memory resource that uses new and delete, serving as the default for pmr::polymorphic_allocator unless otherwise specified.
19:45 Polymorphic Allocator of byte: The speaker advocates for using pmr::polymorphic_allocator<byte> (where byte is the C++17 byte type) as the primary allocator type for its simplicity, understandability, and ease of use with the scoped allocator model.
24:41 Standard Memory Resources: C++17 provides several standard memory resources including new_delete_resource, null_memory_resource (throws on allocation), synchronized_pool_resource, unsynchronized_pool_resource, and monotonic_buffer_resource. These can be used directly or as upstream resources for custom allocators.
29:15 Building a Test Resource: The speaker walks through building a custom test_resource that tracks allocation/deallocation statistics, checks for mismatches, and detects memory leaks.
38:25 Summary of Memory Resource Creation: The speaker summarizes the steps involved in creating a new memory resource: derive from pmr::memory_resource, override do_allocate, do_deallocate, and do_is_equal (usually for identity comparison).
39:17 Building a Container (Singly Linked List): The speaker demonstrates how to build a custom container (a singly linked list called slist) that utilizes pmr::polymorphic_allocator<byte>.
44:04 Boilerplate Reduction: The use of pmr::polymorphic_allocator<byte> eliminates the need for template arguments related to allocators, simplifies constructor definitions, and removes the need for allocator traits and propagation traits.
44:58 Constructor Variants: All constructors of the slist container have an overload that accepts a pmr::polymorphic_allocator<byte>.
46:18 In-Place Construction: The emplace function of slist demonstrates the key pattern for allocating memory and constructing elements: allocate raw bytes for the node using the memory resource, then use pmr::polymorphic_allocator::construct to construct the element within the allocated memory. construct will automatically propagate the allocator to elements that require it.
49:53 Erasing Elements: Erasing elements involves the reverse process: calling destroy on the element and then deallocating the node's memory.
50:37 Move Operations: Move assignment and construction handle the case where allocators are different between source and destination. If allocators are equal, a fast "guts" move is performed. Otherwise, a copy is necessary to ensure proper allocation with the destination's allocator.
55:41 Code Example: A code example demonstrates the usage of test_resource and slist, showing how memory is allocated and deallocated.
56:57 Benefits of Consistent pmr::polymorphic_allocator<byte> Use: The consistent use of pmr::polymorphic_allocator<byte> significantly reduces boilerplate, simplifies code, and avoids the complexities of older allocator models.
59:38 Conclusion: The speaker concludes by emphasizing that while historical complexity exists, C++17 features, particularly polymorphic memory resources and allocators, allow developers to largely ignore it and adopt a simpler, more user-friendly approach to custom memory management. This approach makes allocators a more accessible and powerful tool for developers. [From zhaoli2984's Comment] Allocators, when used well, provide significant benefits.