1// <memory_resource> -*- C++ -*- 2 3// Copyright (C) 2018-2021 Free Software Foundation, Inc. 4// 5// This file is part of the GNU ISO C++ Library. This library is free 6// software; you can redistribute it and/or modify it under the 7// terms of the GNU General Public License as published by the 8// Free Software Foundation; either version 3, or (at your option) 9// any later version. 10 11// This library is distributed in the hope that it will be useful, 12// but WITHOUT ANY WARRANTY; without even the implied warranty of 13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14// GNU General Public License for more details. 15 16// Under Section 7 of GPL version 3, you are granted additional 17// permissions described in the GCC Runtime Library Exception, version 18// 3.1, as published by the Free Software Foundation. 19 20// You should have received a copy of the GNU General Public License and 21// a copy of the GCC Runtime Library Exception along with this program; 22// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see 23// <http://www.gnu.org/licenses/>. 24 25/** @file include/memory_resource 26 * This is a Standard C++ Library header. 27 */ 28 29#ifndef _GLIBCXX_MEMORY_RESOURCE 30#define _GLIBCXX_MEMORY_RESOURCE 1 31 32#pragma GCC system_header 33 34#if __cplusplus >= 201703L 35 36#include <vector> // vector 37#include <cstddef> // size_t, max_align_t, byte 38#include <shared_mutex> // shared_mutex 39#include <bits/align.h> // align 40#include <bits/functexcept.h> // __throw_bad_array_new_length 41#include <bits/uses_allocator.h> // __use_alloc 42#include <bits/uses_allocator_args.h> // uninitialized_construct_using_alloc 43#include <ext/numeric_traits.h> 44#include <debug/assertions.h> 45 46#if ! __cpp_lib_make_obj_using_allocator 47# include <utility> // pair, index_sequence 48# include <tuple> // tuple, forward_as_tuple 49#endif 50 51namespace std _GLIBCXX_VISIBILITY(default) 52{ 53_GLIBCXX_BEGIN_NAMESPACE_VERSION 54namespace pmr 55{ 56#ifdef _GLIBCXX_HAS_GTHREADS 57 // Header and all contents are present. 58# define __cpp_lib_memory_resource 201603L 59#else 60 // The pmr::synchronized_pool_resource type is missing. 61# define __cpp_lib_memory_resource 1 62#endif 63 64 class memory_resource; 65 66#if __cplusplus == 201703L 67 template<typename _Tp> 68 class polymorphic_allocator; 69#else // C++20 70# define __cpp_lib_polymorphic_allocator 201902L 71 template<typename _Tp = std::byte> 72 class polymorphic_allocator; 73#endif 74 75 // Global memory resources 76 memory_resource* new_delete_resource() noexcept; 77 memory_resource* null_memory_resource() noexcept; 78 memory_resource* set_default_resource(memory_resource* __r) noexcept; 79 memory_resource* get_default_resource() noexcept 80 __attribute__((__returns_nonnull__)); 81 82 // Pool resource classes 83 struct pool_options; 84#ifdef _GLIBCXX_HAS_GTHREADS 85 class synchronized_pool_resource; 86#endif 87 class unsynchronized_pool_resource; 88 class monotonic_buffer_resource; 89 90 /// Class memory_resource 91 class memory_resource 92 { 93 static constexpr size_t _S_max_align = alignof(max_align_t); 94 95 public: 96 memory_resource() = default; 97 memory_resource(const memory_resource&) = default; 98 virtual ~memory_resource(); // key function 99 100 memory_resource& operator=(const memory_resource&) = default; 101 102 [[nodiscard]] 103 void* 104 allocate(size_t __bytes, size_t __alignment = _S_max_align) 105 __attribute__((__returns_nonnull__,__alloc_size__(2),__alloc_align__(3))) 106 { return do_allocate(__bytes, __alignment); } 107 108 void 109 deallocate(void* __p, size_t __bytes, size_t __alignment = _S_max_align) 110 __attribute__((__nonnull__)) 111 { return do_deallocate(__p, __bytes, __alignment); } 112 113 bool 114 is_equal(const memory_resource& __other) const noexcept 115 { return do_is_equal(__other); } 116 117 private: 118 virtual void* 119 do_allocate(size_t __bytes, size_t __alignment) = 0; 120 121 virtual void 122 do_deallocate(void* __p, size_t __bytes, size_t __alignment) = 0; 123 124 virtual bool 125 do_is_equal(const memory_resource& __other) const noexcept = 0; 126 }; 127 128 inline bool 129 operator==(const memory_resource& __a, const memory_resource& __b) noexcept 130 { return &__a == &__b || __a.is_equal(__b); } 131 132#if __cpp_impl_three_way_comparison < 201907L 133 inline bool 134 operator!=(const memory_resource& __a, const memory_resource& __b) noexcept 135 { return !(__a == __b); } 136#endif 137 138 // C++17 23.12.3 Class template polymorphic_allocator 139 template<typename _Tp> 140 class polymorphic_allocator 141 { 142 // _GLIBCXX_RESOLVE_LIB_DEFECTS 143 // 2975. Missing case for pair construction in polymorphic allocators 144 template<typename _Up> 145 struct __not_pair { using type = void; }; 146 147 template<typename _Up1, typename _Up2> 148 struct __not_pair<pair<_Up1, _Up2>> { }; 149 150 public: 151 using value_type = _Tp; 152 153 polymorphic_allocator() noexcept 154 : _M_resource(get_default_resource()) 155 { } 156 157 polymorphic_allocator(memory_resource* __r) noexcept 158 __attribute__((__nonnull__)) 159 : _M_resource(__r) 160 { _GLIBCXX_DEBUG_ASSERT(__r); } 161 162 polymorphic_allocator(const polymorphic_allocator& __other) = default; 163 164 template<typename _Up> 165 polymorphic_allocator(const polymorphic_allocator<_Up>& __x) noexcept 166 : _M_resource(__x.resource()) 167 { } 168 169 polymorphic_allocator& 170 operator=(const polymorphic_allocator&) = delete; 171 172 [[nodiscard]] 173 _Tp* 174 allocate(size_t __n) 175 __attribute__((__returns_nonnull__)) 176 { 177 if ((__gnu_cxx::__int_traits<size_t>::__max / sizeof(_Tp)) < __n) 178 std::__throw_bad_array_new_length(); 179 return static_cast<_Tp*>(_M_resource->allocate(__n * sizeof(_Tp), 180 alignof(_Tp))); 181 } 182 183 void 184 deallocate(_Tp* __p, size_t __n) noexcept 185 __attribute__((__nonnull__)) 186 { _M_resource->deallocate(__p, __n * sizeof(_Tp), alignof(_Tp)); } 187 188#if __cplusplus > 201703L 189 [[nodiscard]] void* 190 allocate_bytes(size_t __nbytes, 191 size_t __alignment = alignof(max_align_t)) 192 { return _M_resource->allocate(__nbytes, __alignment); } 193 194 void 195 deallocate_bytes(void* __p, size_t __nbytes, 196 size_t __alignment = alignof(max_align_t)) 197 { _M_resource->deallocate(__p, __nbytes, __alignment); } 198 199 template<typename _Up> 200 [[nodiscard]] _Up* 201 allocate_object(size_t __n = 1) 202 { 203 if ((__gnu_cxx::__int_traits<size_t>::__max / sizeof(_Up)) < __n) 204 std::__throw_bad_array_new_length(); 205 return static_cast<_Up*>(allocate_bytes(__n * sizeof(_Up), 206 alignof(_Up))); 207 } 208 209 template<typename _Up> 210 void 211 deallocate_object(_Up* __p, size_t __n = 1) 212 { deallocate_bytes(__p, __n * sizeof(_Up), alignof(_Up)); } 213 214 template<typename _Up, typename... _CtorArgs> 215 [[nodiscard]] _Up* 216 new_object(_CtorArgs&&... __ctor_args) 217 { 218 _Up* __p = allocate_object<_Up>(); 219 __try 220 { 221 construct(__p, std::forward<_CtorArgs>(__ctor_args)...); 222 } 223 __catch (...) 224 { 225 deallocate_object(__p); 226 __throw_exception_again; 227 } 228 return __p; 229 } 230 231 template<typename _Up> 232 void 233 delete_object(_Up* __p) 234 { 235 destroy(__p); 236 deallocate_object(__p); 237 } 238#endif // C++2a 239 240#if ! __cpp_lib_make_obj_using_allocator 241 template<typename _Tp1, typename... _Args> 242 __attribute__((__nonnull__)) 243 typename __not_pair<_Tp1>::type 244 construct(_Tp1* __p, _Args&&... __args) 245 { 246 // _GLIBCXX_RESOLVE_LIB_DEFECTS 247 // 2969. polymorphic_allocator::construct() shouldn't pass resource() 248 using __use_tag 249 = std::__uses_alloc_t<_Tp1, polymorphic_allocator, _Args...>; 250 if constexpr (is_base_of_v<__uses_alloc0, __use_tag>) 251 ::new(__p) _Tp1(std::forward<_Args>(__args)...); 252 else if constexpr (is_base_of_v<__uses_alloc1_, __use_tag>) 253 ::new(__p) _Tp1(allocator_arg, *this, 254 std::forward<_Args>(__args)...); 255 else 256 ::new(__p) _Tp1(std::forward<_Args>(__args)..., *this); 257 } 258 259 template<typename _Tp1, typename _Tp2, 260 typename... _Args1, typename... _Args2> 261 __attribute__((__nonnull__)) 262 void 263 construct(pair<_Tp1, _Tp2>* __p, piecewise_construct_t, 264 tuple<_Args1...> __x, tuple<_Args2...> __y) 265 { 266 auto __x_tag = 267 __use_alloc<_Tp1, polymorphic_allocator, _Args1...>(*this); 268 auto __y_tag = 269 __use_alloc<_Tp2, polymorphic_allocator, _Args2...>(*this); 270 index_sequence_for<_Args1...> __x_i; 271 index_sequence_for<_Args2...> __y_i; 272 273 ::new(__p) pair<_Tp1, _Tp2>(piecewise_construct, 274 _S_construct_p(__x_tag, __x_i, __x), 275 _S_construct_p(__y_tag, __y_i, __y)); 276 } 277 278 template<typename _Tp1, typename _Tp2> 279 __attribute__((__nonnull__)) 280 void 281 construct(pair<_Tp1, _Tp2>* __p) 282 { this->construct(__p, piecewise_construct, tuple<>(), tuple<>()); } 283 284 template<typename _Tp1, typename _Tp2, typename _Up, typename _Vp> 285 __attribute__((__nonnull__)) 286 void 287 construct(pair<_Tp1, _Tp2>* __p, _Up&& __x, _Vp&& __y) 288 { 289 this->construct(__p, piecewise_construct, 290 std::forward_as_tuple(std::forward<_Up>(__x)), 291 std::forward_as_tuple(std::forward<_Vp>(__y))); 292 } 293 294 template <typename _Tp1, typename _Tp2, typename _Up, typename _Vp> 295 __attribute__((__nonnull__)) 296 void 297 construct(pair<_Tp1, _Tp2>* __p, const std::pair<_Up, _Vp>& __pr) 298 { 299 this->construct(__p, piecewise_construct, 300 std::forward_as_tuple(__pr.first), 301 std::forward_as_tuple(__pr.second)); 302 } 303 304 template<typename _Tp1, typename _Tp2, typename _Up, typename _Vp> 305 __attribute__((__nonnull__)) 306 void 307 construct(pair<_Tp1, _Tp2>* __p, pair<_Up, _Vp>&& __pr) 308 { 309 this->construct(__p, piecewise_construct, 310 std::forward_as_tuple(std::forward<_Up>(__pr.first)), 311 std::forward_as_tuple(std::forward<_Vp>(__pr.second))); 312 } 313#else // make_obj_using_allocator 314 template<typename _Tp1, typename... _Args> 315 __attribute__((__nonnull__)) 316 void 317 construct(_Tp1* __p, _Args&&... __args) 318 { 319 std::uninitialized_construct_using_allocator(__p, *this, 320 std::forward<_Args>(__args)...); 321 } 322#endif 323 324 template<typename _Up> 325 __attribute__((__nonnull__)) 326 void 327 destroy(_Up* __p) 328 { __p->~_Up(); } 329 330 polymorphic_allocator 331 select_on_container_copy_construction() const noexcept 332 { return polymorphic_allocator(); } 333 334 memory_resource* 335 resource() const noexcept 336 __attribute__((__returns_nonnull__)) 337 { return _M_resource; } 338 339 private: 340 using __uses_alloc1_ = __uses_alloc1<polymorphic_allocator>; 341 using __uses_alloc2_ = __uses_alloc2<polymorphic_allocator>; 342 343#if ! __cpp_lib_make_obj_using_allocator 344 template<typename _Ind, typename... _Args> 345 static tuple<_Args&&...> 346 _S_construct_p(__uses_alloc0, _Ind, tuple<_Args...>& __t) 347 { return std::move(__t); } 348 349 template<size_t... _Ind, typename... _Args> 350 static tuple<allocator_arg_t, polymorphic_allocator, _Args&&...> 351 _S_construct_p(__uses_alloc1_ __ua, index_sequence<_Ind...>, 352 tuple<_Args...>& __t) 353 { 354 return { 355 allocator_arg, *__ua._M_a, std::get<_Ind>(std::move(__t))... 356 }; 357 } 358 359 template<size_t... _Ind, typename... _Args> 360 static tuple<_Args&&..., polymorphic_allocator> 361 _S_construct_p(__uses_alloc2_ __ua, index_sequence<_Ind...>, 362 tuple<_Args...>& __t) 363 { return { std::get<_Ind>(std::move(__t))..., *__ua._M_a }; } 364#endif 365 366 memory_resource* _M_resource; 367 }; 368 369 template<typename _Tp1, typename _Tp2> 370 inline bool 371 operator==(const polymorphic_allocator<_Tp1>& __a, 372 const polymorphic_allocator<_Tp2>& __b) noexcept 373 { return *__a.resource() == *__b.resource(); } 374 375#if __cpp_impl_three_way_comparison < 201907L 376 template<typename _Tp1, typename _Tp2> 377 inline bool 378 operator!=(const polymorphic_allocator<_Tp1>& __a, 379 const polymorphic_allocator<_Tp2>& __b) noexcept 380 { return !(__a == __b); } 381#endif 382 383 /// Parameters for tuning a pool resource's behaviour. 384 struct pool_options 385 { 386 /** @brief Upper limit on number of blocks in a chunk. 387 * 388 * A lower value prevents allocating huge chunks that could remain mostly 389 * unused, but means pools will need to replenished more frequently. 390 */ 391 size_t max_blocks_per_chunk = 0; 392 393 /* @brief Largest block size (in bytes) that should be served from pools. 394 * 395 * Larger allocations will be served directly by the upstream resource, 396 * not from one of the pools managed by the pool resource. 397 */ 398 size_t largest_required_pool_block = 0; 399 }; 400 401 // Common implementation details for un-/synchronized pool resources. 402 class __pool_resource 403 { 404 friend class synchronized_pool_resource; 405 friend class unsynchronized_pool_resource; 406 407 __pool_resource(const pool_options& __opts, memory_resource* __upstream); 408 409 ~__pool_resource(); 410 411 __pool_resource(const __pool_resource&) = delete; 412 __pool_resource& operator=(const __pool_resource&) = delete; 413 414 // Allocate a large unpooled block. 415 void* 416 allocate(size_t __bytes, size_t __alignment); 417 418 // Deallocate a large unpooled block. 419 void 420 deallocate(void* __p, size_t __bytes, size_t __alignment); 421 422 423 // Deallocate unpooled memory. 424 void release() noexcept; 425 426 memory_resource* resource() const noexcept 427 { return _M_unpooled.get_allocator().resource(); } 428 429 struct _Pool; 430 431 _Pool* _M_alloc_pools(); 432 433 const pool_options _M_opts; 434 435 struct _BigBlock; 436 // Collection of blocks too big for any pool, sorted by address. 437 // This also stores the only copy of the upstream memory resource pointer. 438 _GLIBCXX_STD_C::pmr::vector<_BigBlock> _M_unpooled; 439 440 const int _M_npools; 441 }; 442 443#ifdef _GLIBCXX_HAS_GTHREADS 444 /// A thread-safe memory resource that manages pools of fixed-size blocks. 445 class synchronized_pool_resource : public memory_resource 446 { 447 public: 448 synchronized_pool_resource(const pool_options& __opts, 449 memory_resource* __upstream) 450 __attribute__((__nonnull__)); 451 452 synchronized_pool_resource() 453 : synchronized_pool_resource(pool_options(), get_default_resource()) 454 { } 455 456 explicit 457 synchronized_pool_resource(memory_resource* __upstream) 458 __attribute__((__nonnull__)) 459 : synchronized_pool_resource(pool_options(), __upstream) 460 { } 461 462 explicit 463 synchronized_pool_resource(const pool_options& __opts) 464 : synchronized_pool_resource(__opts, get_default_resource()) { } 465 466 synchronized_pool_resource(const synchronized_pool_resource&) = delete; 467 468 virtual ~synchronized_pool_resource(); 469 470 synchronized_pool_resource& 471 operator=(const synchronized_pool_resource&) = delete; 472 473 void release(); 474 475 memory_resource* 476 upstream_resource() const noexcept 477 __attribute__((__returns_nonnull__)) 478 { return _M_impl.resource(); } 479 480 pool_options options() const noexcept { return _M_impl._M_opts; } 481 482 protected: 483 void* 484 do_allocate(size_t __bytes, size_t __alignment) override; 485 486 void 487 do_deallocate(void* __p, size_t __bytes, size_t __alignment) override; 488 489 bool 490 do_is_equal(const memory_resource& __other) const noexcept override 491 { return this == &__other; } 492 493 public: 494 // Thread-specific pools (only public for access by implementation details) 495 struct _TPools; 496 497 private: 498 _TPools* _M_alloc_tpools(lock_guard<shared_mutex>&); 499 _TPools* _M_alloc_shared_tpools(lock_guard<shared_mutex>&); 500 auto _M_thread_specific_pools() noexcept; 501 502 __pool_resource _M_impl; 503 __gthread_key_t _M_key; 504 // Linked list of thread-specific pools. All threads share _M_tpools[0]. 505 _TPools* _M_tpools = nullptr; 506 mutable shared_mutex _M_mx; 507 }; 508#endif 509 510 /// A non-thread-safe memory resource that manages pools of fixed-size blocks. 511 class unsynchronized_pool_resource : public memory_resource 512 { 513 public: 514 [[__gnu__::__nonnull__]] 515 unsynchronized_pool_resource(const pool_options& __opts, 516 memory_resource* __upstream); 517 518 unsynchronized_pool_resource() 519 : unsynchronized_pool_resource(pool_options(), get_default_resource()) 520 { } 521 522 [[__gnu__::__nonnull__]] 523 explicit 524 unsynchronized_pool_resource(memory_resource* __upstream) 525 : unsynchronized_pool_resource(pool_options(), __upstream) 526 { } 527 528 explicit 529 unsynchronized_pool_resource(const pool_options& __opts) 530 : unsynchronized_pool_resource(__opts, get_default_resource()) { } 531 532 unsynchronized_pool_resource(const unsynchronized_pool_resource&) = delete; 533 534 virtual ~unsynchronized_pool_resource(); 535 536 unsynchronized_pool_resource& 537 operator=(const unsynchronized_pool_resource&) = delete; 538 539 void release(); 540 541 [[__gnu__::__returns_nonnull__]] 542 memory_resource* 543 upstream_resource() const noexcept 544 { return _M_impl.resource(); } 545 546 pool_options options() const noexcept { return _M_impl._M_opts; } 547 548 protected: 549 void* 550 do_allocate(size_t __bytes, size_t __alignment) override; 551 552 void 553 do_deallocate(void* __p, size_t __bytes, size_t __alignment) override; 554 555 bool 556 do_is_equal(const memory_resource& __other) const noexcept override 557 { return this == &__other; } 558 559 private: 560 using _Pool = __pool_resource::_Pool; 561 562 auto _M_find_pool(size_t) noexcept; 563 564 __pool_resource _M_impl; 565 _Pool* _M_pools = nullptr; 566 }; 567 568 class monotonic_buffer_resource : public memory_resource 569 { 570 public: 571 explicit 572 monotonic_buffer_resource(memory_resource* __upstream) noexcept 573 __attribute__((__nonnull__)) 574 : _M_upstream(__upstream) 575 { _GLIBCXX_DEBUG_ASSERT(__upstream != nullptr); } 576 577 monotonic_buffer_resource(size_t __initial_size, 578 memory_resource* __upstream) noexcept 579 __attribute__((__nonnull__)) 580 : _M_next_bufsiz(__initial_size), 581 _M_upstream(__upstream) 582 { 583 _GLIBCXX_DEBUG_ASSERT(__upstream != nullptr); 584 _GLIBCXX_DEBUG_ASSERT(__initial_size > 0); 585 } 586 587 monotonic_buffer_resource(void* __buffer, size_t __buffer_size, 588 memory_resource* __upstream) noexcept 589 __attribute__((__nonnull__(4))) 590 : _M_current_buf(__buffer), _M_avail(__buffer_size), 591 _M_next_bufsiz(_S_next_bufsize(__buffer_size)), 592 _M_upstream(__upstream), 593 _M_orig_buf(__buffer), _M_orig_size(__buffer_size) 594 { 595 _GLIBCXX_DEBUG_ASSERT(__upstream != nullptr); 596 _GLIBCXX_DEBUG_ASSERT(__buffer != nullptr || __buffer_size == 0); 597 } 598 599 monotonic_buffer_resource() noexcept 600 : monotonic_buffer_resource(get_default_resource()) 601 { } 602 603 explicit 604 monotonic_buffer_resource(size_t __initial_size) noexcept 605 : monotonic_buffer_resource(__initial_size, get_default_resource()) 606 { } 607 608 monotonic_buffer_resource(void* __buffer, size_t __buffer_size) noexcept 609 : monotonic_buffer_resource(__buffer, __buffer_size, get_default_resource()) 610 { } 611 612 monotonic_buffer_resource(const monotonic_buffer_resource&) = delete; 613 614 virtual ~monotonic_buffer_resource(); // key function 615 616 monotonic_buffer_resource& 617 operator=(const monotonic_buffer_resource&) = delete; 618 619 void 620 release() noexcept 621 { 622 if (_M_head) 623 _M_release_buffers(); 624 625 // reset to initial state at contruction: 626 if ((_M_current_buf = _M_orig_buf)) 627 { 628 _M_avail = _M_orig_size; 629 _M_next_bufsiz = _S_next_bufsize(_M_orig_size); 630 } 631 else 632 { 633 _M_avail = 0; 634 _M_next_bufsiz = _M_orig_size; 635 } 636 } 637 638 memory_resource* 639 upstream_resource() const noexcept 640 __attribute__((__returns_nonnull__)) 641 { return _M_upstream; } 642 643 protected: 644 void* 645 do_allocate(size_t __bytes, size_t __alignment) override 646 { 647 if (__builtin_expect(__bytes == 0, false)) 648 __bytes = 1; // Ensures we don't return the same pointer twice. 649 650 void* __p = std::align(__alignment, __bytes, _M_current_buf, _M_avail); 651 if (__builtin_expect(__p == nullptr, false)) 652 { 653 _M_new_buffer(__bytes, __alignment); 654 __p = _M_current_buf; 655 } 656 _M_current_buf = (char*)_M_current_buf + __bytes; 657 _M_avail -= __bytes; 658 return __p; 659 } 660 661 void 662 do_deallocate(void*, size_t, size_t) override 663 { } 664 665 bool 666 do_is_equal(const memory_resource& __other) const noexcept override 667 { return this == &__other; } 668 669 private: 670 // Update _M_current_buf and _M_avail to refer to a new buffer with 671 // at least the specified size and alignment, allocated from upstream. 672 void 673 _M_new_buffer(size_t __bytes, size_t __alignment); 674 675 // Deallocate all buffers obtained from upstream. 676 void 677 _M_release_buffers() noexcept; 678 679 static size_t 680 _S_next_bufsize(size_t __buffer_size) noexcept 681 { 682 if (__builtin_expect(__buffer_size == 0, false)) 683 __buffer_size = 1; 684 return __buffer_size * _S_growth_factor; 685 } 686 687 static constexpr size_t _S_init_bufsize = 128 * sizeof(void*); 688 static constexpr float _S_growth_factor = 1.5; 689 690 void* _M_current_buf = nullptr; 691 size_t _M_avail = 0; 692 size_t _M_next_bufsiz = _S_init_bufsize; 693 694 // Initial values set at construction and reused by release(): 695 memory_resource* const _M_upstream; 696 void* const _M_orig_buf = nullptr; 697 size_t const _M_orig_size = _M_next_bufsiz; 698 699 class _Chunk; 700 _Chunk* _M_head = nullptr; 701 }; 702 703} // namespace pmr 704_GLIBCXX_END_NAMESPACE_VERSION 705} // namespace std 706 707#endif // C++17 708#endif // _GLIBCXX_MEMORY_RESOURCE 709