├── devector.h ├── docs.pdf ├── license.txt └── readme.md /devector.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014 Orson Peters 3 | 4 | This software is provided 'as-is', without any express or implied warranty. In no event will the 5 | authors be held liable for any damages arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, including commercial 8 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 9 | 10 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the 11 | original software. If you use this software in a product, an acknowledgement in the product 12 | documentation would be appreciated but is not required. 13 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 14 | being the original software. 15 | 3. This notice may not be removed or altered from any source distribution. 16 | */ 17 | 18 | 19 | #ifndef DEVECTOR_H 20 | #define DEVECTOR_H 21 | 22 | // TODO: Include what you use. 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | 29 | 30 | // There is an issue with std::swap. It uses a recursive noexcept declaration for multidimensional 31 | // arrays, but those do not work. This code defines detail::is_nothrow_swappable to solve that. 32 | // http://stackoverflow.com/questions/26793979/why-is-swapping-multidimensional-arrays-not-noexcept 33 | namespace detail { 34 | namespace swap_adl_tests { 35 | // If swap ADL finds this then it would call std::swap otherwise (same signature). 36 | struct tag {}; 37 | 38 | template tag swap(T&, T&); 39 | template tag swap(T (&a)[N], T (&b)[N]); 40 | 41 | // Helper functions to test if an unqualified swap is possible, and if it becomes std::swap. 42 | template std::false_type can_swap(...) noexcept(false); 43 | template(), std::declval()))> 44 | std::true_type can_swap(int) noexcept( 45 | noexcept(swap(std::declval(), std::declval())) 46 | ); 47 | 48 | template std::false_type uses_std(...); 49 | template 50 | std::is_same(), std::declval())), tag> uses_std(int); 51 | 52 | // Helper templates to determine if swapping is noexcept. The C++11/14 standards have a 53 | // broken noexcept specification for multidimensional arrays, so we use a template solution. 54 | template 55 | struct is_std_swap_noexcept : std::integral_constant::value && 57 | std::is_nothrow_move_assignable::value 58 | > { }; 59 | 60 | template 61 | struct is_std_swap_noexcept : is_std_swap_noexcept { }; 62 | 63 | template 64 | struct is_adl_swap_noexcept : std::integral_constant(0))> { }; 65 | } 66 | 67 | template 68 | struct is_swappable : std::integral_constant(0))::value && 70 | (!decltype(detail::swap_adl_tests::uses_std(0))::value || 71 | (std::is_move_assignable::value && std::is_move_constructible::value)) 72 | > {}; 73 | 74 | template 75 | struct is_swappable : std::integral_constant(0))::value && 77 | (!decltype(detail::swap_adl_tests::uses_std(0))::value || 78 | is_swappable::value) 79 | > {}; 80 | 81 | template 82 | struct is_nothrow_swappable : std::integral_constant::value && ( 84 | (decltype(detail::swap_adl_tests::uses_std(0))::value && 85 | detail::swap_adl_tests::is_std_swap_noexcept::value) 86 | || 87 | (!decltype(detail::swap_adl_tests::uses_std(0))::value && 88 | detail::swap_adl_tests::is_adl_swap_noexcept::value) 89 | ) 90 | > {}; 91 | 92 | template 93 | using move_if_noexcept_iterator = typename std::conditional< 94 | !std::is_nothrow_move_constructible< 95 | typename std::iterator_traits::value_type 96 | >::value && std::is_copy_constructible< 97 | typename std::iterator_traits::value_type 98 | >::value, 99 | Iterator, 100 | std::move_iterator 101 | >::type; 102 | 103 | template 104 | constexpr move_if_noexcept_iterator make_move_if_noexcept_iterator(Iterator i) { 105 | return move_if_noexcept_iterator(i); 106 | } 107 | } 108 | 109 | 110 | template> 111 | class devector { 112 | private: 113 | typedef std::allocator_traits alloc_traits; 114 | typedef devector V; 115 | 116 | public: 117 | // Typedefs. 118 | typedef T value_type; 119 | typedef Allocator allocator_type; 120 | typedef typename alloc_traits::size_type size_type; 121 | typedef typename alloc_traits::difference_type difference_type; 122 | typedef typename alloc_traits::pointer pointer; 123 | typedef typename alloc_traits::const_pointer const_pointer; 124 | typedef T& reference; 125 | typedef const T& const_reference; 126 | typedef pointer iterator; 127 | typedef const_pointer const_iterator; 128 | typedef std::reverse_iterator reverse_iterator; 129 | typedef std::reverse_iterator const_reverse_iterator; 130 | 131 | // Construct/copy/destroy. 132 | ~devector() noexcept { destruct(); } 133 | 134 | devector() noexcept(std::is_nothrow_default_constructible::value) : impl() { 135 | impl.null(); 136 | } 137 | 138 | explicit devector(const Allocator& alloc) noexcept : impl(alloc) { impl.null(); } 139 | 140 | explicit devector(size_type n, const Allocator& alloc = Allocator()) : impl(alloc) { 141 | impl.begin_storage = impl.begin_cursor = alloc_traits::allocate(impl, n); 142 | impl.end_storage = impl.end_cursor = impl.begin_storage + n; 143 | try { alloc_uninitialized_fill(impl.begin_cursor, impl.end_cursor); } 144 | catch (...) { deallocate(); throw; } 145 | } 146 | 147 | devector(size_type n, const T& value, const Allocator& alloc = Allocator()) : impl(alloc) { 148 | impl.begin_storage = impl.begin_cursor = alloc_traits::allocate(impl, n); 149 | impl.end_storage = impl.end_cursor = impl.begin_storage + n; 150 | try { alloc_uninitialized_fill(impl.begin_cursor, impl.end_cursor, value); } 151 | catch (...) { deallocate(); throw; } 152 | } 153 | 154 | template 155 | devector(InputIterator first, InputIterator last, const Allocator& alloc = Allocator()) 156 | : impl(alloc) { 157 | init_range(first, last, typename std::iterator_traits::iterator_category()); 158 | } 159 | 160 | devector(const V& other) 161 | : impl(alloc_traits::select_on_container_copy_construction(other.impl.alloc())) { 162 | init_range(other.begin(), other.end(), std::random_access_iterator_tag()); 163 | } 164 | 165 | devector(const V& other, const Allocator& alloc) : impl(alloc) { 166 | init_range(other.begin(), other.end(), std::random_access_iterator_tag()); 167 | } 168 | 169 | devector(V&& other) noexcept : impl(std::move(other.impl.alloc())) { 170 | impl.storage() = std::move(other.impl.storage()); 171 | other.impl.null(); 172 | } 173 | 174 | devector(V&& other, const Allocator& alloc) : impl(alloc) { 175 | if (impl.alloc() == other.impl.alloc()) { 176 | impl.storage() = std::move(other.impl.storage()); 177 | } else { 178 | init_range(std::move_iterator(other.begin()), 179 | std::move_iterator(other.end()), 180 | std::random_access_iterator_tag()); 181 | other.destruct(); 182 | } 183 | 184 | other.impl.null(); 185 | } 186 | 187 | devector(std::initializer_list il, const Allocator& alloc = Allocator()) : impl(alloc) { 188 | init_range(il.begin(), il.end(), std::random_access_iterator_tag()); 189 | } 190 | 191 | V& operator=(const V& other) { 192 | if (this != &other) { 193 | copy_assign_propagate_dispatcher( 194 | other, 195 | std::integral_constant() 198 | ); 199 | } 200 | 201 | return *this; 202 | } 203 | 204 | V& operator=(V&& other) noexcept(alloc_traits::propagate_on_container_move_assignment::value) { 205 | if (this != &other) { 206 | move_assign_propagate_dispatcher( 207 | std::move(other), 208 | std::integral_constant() 211 | ); 212 | } 213 | 214 | return *this; 215 | } 216 | 217 | V& operator=(std::initializer_list il) { assign(il); return *this; } 218 | 219 | template 220 | typename std::enable_if< 221 | std::is_base_of< 222 | std::input_iterator_tag, 223 | typename std::iterator_traits::iterator_category 224 | >::value, 225 | void>::type assign(InputIterator first, InputIterator last) { 226 | assign_range(first, last, 227 | typename std::iterator_traits::iterator_category()); 228 | } 229 | 230 | void assign(size_type n, const T& t) { 231 | reserve(n); 232 | while (size() > n) pop_back(); 233 | for (iterator it = begin(); it != end(); ++it) *it = t; 234 | while (size() < n) push_back(t); 235 | } 236 | 237 | void assign(std::initializer_list il) { assign(il.begin(), il.end()); } 238 | 239 | allocator_type get_allocator() const noexcept { return impl; } 240 | 241 | // Iterators. 242 | iterator begin() noexcept { return iterator(impl.begin_cursor); } 243 | const_iterator begin() const noexcept { return iterator(impl.begin_cursor); } 244 | iterator end() noexcept { return iterator(impl.end_cursor); } 245 | const_iterator end() const noexcept { return iterator(impl.end_cursor); } 246 | 247 | reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } 248 | const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } 249 | reverse_iterator rend() noexcept { return reverse_iterator(begin()); } 250 | const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } 251 | 252 | const_iterator cbegin() const noexcept { return begin(); } 253 | const_iterator cend() const noexcept { return end(); } 254 | const_reverse_iterator crbegin() const noexcept { return rbegin(); } 255 | const_reverse_iterator crend() const noexcept { return rend(); } 256 | 257 | // Capacity. 258 | size_type max_size() const noexcept { return impl.max_size(); } 259 | size_type size() const noexcept { return impl.end_cursor - impl.begin_cursor; } 260 | size_type capacity() const noexcept { return impl.end_storage - impl.begin_storage; } 261 | size_type capacity_front() const noexcept { return impl.end_cursor - impl.begin_storage; } 262 | size_type capacity_back() const noexcept { return impl.end_storage - impl.begin_cursor; } 263 | 264 | void resize(size_type n) { resize_back_impl(n); } 265 | void resize(size_type n, const T& t) { resize_back_impl(n, t); } 266 | void resize_back(size_type n) { resize_back_impl(n); } 267 | void resize_back(size_type n, const T& t) { resize_back_impl(n, t); } 268 | void resize_front(size_type n) { resize_front_impl(n); } 269 | void resize_front(size_type n, const T& t) { resize_front_impl(n, t); } 270 | 271 | void reserve(size_type n) { reserve_back(n); } 272 | 273 | void reserve(size_type new_front, size_type new_back) { 274 | if (new_front > max_size() || new_back > max_size()) throw std::length_error("devector"); 275 | if (capacity_front() >= new_front && capacity_back() >= new_back) return; 276 | 277 | reallocate(new_front - size(), new_back - size()); 278 | } 279 | 280 | void reserve_front(size_type n) { 281 | if (n > max_size()) throw std::length_error("devector"); 282 | if (capacity_front() >= n) return; 283 | 284 | reallocate(n - size(), impl.end_storage - impl.end_cursor); 285 | } 286 | 287 | void reserve_back(size_type n) { 288 | if (n > max_size()) throw std::length_error("devector"); 289 | if (capacity_back() >= n) return; 290 | 291 | reallocate(impl.begin_cursor - impl.begin_storage, n - size()); 292 | } 293 | 294 | void shrink_to_fit() { 295 | if (capacity() <= size()) return; 296 | 297 | V(detail::make_move_if_noexcept_iterator(begin()), 298 | detail::make_move_if_noexcept_iterator(end()), 299 | get_allocator()).swap(*this); 300 | } 301 | 302 | bool empty() const noexcept { return impl.begin_cursor == impl.end_cursor; } 303 | 304 | // Indexing. 305 | reference operator[](size_type i) noexcept { return impl.begin_cursor[i]; } 306 | const_reference operator[](size_type i) const noexcept { return impl.begin_cursor[i]; } 307 | 308 | reference at(size_type i) { 309 | if (i >= size()) throw std::out_of_range("devector"); 310 | return (*this)[i]; 311 | } 312 | 313 | const_reference at(size_type i) const { 314 | if (i >= size()) throw std::out_of_range("devector"); 315 | return (*this)[i]; 316 | } 317 | 318 | reference front() noexcept { return *begin(); } 319 | const_reference front() const noexcept { return *begin(); } 320 | reference back() noexcept { return *(end() - 1); } 321 | const_reference back() const noexcept { return *(end() - 1); } 322 | T* data() noexcept { return std::addressof(front()); } 323 | const T* data() const noexcept { return std::addressof(front()); } 324 | 325 | // Modifiers. 326 | void push_front(const T& x) { emplace_front(x); } 327 | void push_front(T&& x) { emplace_front(std::move(x)); } 328 | void push_back(const T& x) { emplace_back(x); } 329 | void push_back(T&& x) { emplace_back(std::move(x)); } 330 | 331 | void pop_front() noexcept { alloc_traits::destroy(impl, std::addressof(*impl.begin_cursor++)); } 332 | void pop_back() noexcept { alloc_traits::destroy(impl, std::addressof(*--impl.end_cursor)); } 333 | 334 | template 335 | void emplace_front(Args&&... args) { 336 | assure_space_front(1); 337 | alloc_traits::construct(impl, std::addressof(*(begin() - 1)), std::forward(args)...); 338 | --impl.begin_cursor; // We do this after constructing for strong exception safety. 339 | } 340 | 341 | template 342 | void emplace_back(Args&&... args) { 343 | assure_space_back(1); 344 | alloc_traits::construct(impl, std::addressof(*end()), std::forward(args)...); 345 | ++impl.end_cursor; // We do this after constructing for strong exception safety. 346 | } 347 | 348 | // CORRECT MARKER 349 | 350 | template 351 | iterator emplace(const_iterator position, Args&&... args) { 352 | // TODO: move to unitialized memory broken 353 | // TODO: strong exception guarantee 354 | difference_type dist_front = position - begin(); 355 | difference_type dist_back = end() - position; 356 | 357 | if (dist_front < dist_back) { 358 | // TODO: do not move data twice 359 | //if (impl.begin_cursor == impl.begin_storage) req_storage_front(); 360 | 361 | if (dist_front == 0) { 362 | alloc_traits::construct(impl, std::addressof(*(begin() - 1)), 363 | std::forward(args)...); 364 | } else { 365 | T tmp(std::forward(args)...); 366 | 367 | try { 368 | alloc_traits::construct(impl, std::addressof(*(begin() - 1)), 369 | std::move_if_noexcept(front())); 370 | } catch (...) { 371 | alloc_traits::destroy(impl, std::addressof(*(begin() - 1))); 372 | throw; 373 | } 374 | 375 | 376 | } 377 | 378 | --impl.begin_cursor; 379 | } else { 380 | } 381 | 382 | // const_iterator to iterator 383 | return begin() + dist_front; 384 | } 385 | 386 | iterator insert(const_iterator position, const T& t) { return emplace(position, t); } 387 | iterator insert(const_iterator position, T&& t) { return emplace(position, std::move(t)); } 388 | iterator insert(const_iterator position, size_type n, const T& t); 389 | 390 | iterator insert(const_iterator position, std::initializer_list il) { 391 | return insert(position, il.begin(), il.end()); 392 | } 393 | 394 | template 395 | typename std::enable_if< 396 | std::is_base_of< 397 | std::input_iterator_tag, 398 | typename std::iterator_traits::iterator_category 399 | >::value, 400 | iterator>::type insert(const_iterator position, InputIterator first, InputIterator last); 401 | 402 | iterator erase(const_iterator position) { return erase(position, position + 1); } 403 | 404 | iterator erase(const_iterator first, const_iterator last) { 405 | difference_type n = last - first; 406 | difference_type retpos = first - begin(); 407 | 408 | if (first - begin() < end() - last) { 409 | std::move_backward(begin(), first, first + n); 410 | while (n--) pop_front(); 411 | } else { 412 | std::move(last, end(), last - n); 413 | while (n--) pop_back(); 414 | } 415 | 416 | return begin() + retpos; 417 | } 418 | 419 | void swap(V& other) 420 | noexcept(!alloc_traits::propagate_on_container_swap::value || 421 | detail::is_nothrow_swappable::value) { 422 | using std::swap; 423 | 424 | if (alloc_traits::propagate_on_container_swap::value) { 425 | swap(impl.alloc(), other.impl.alloc()); 426 | } 427 | 428 | swap(impl.storage(), other.impl.storage()); 429 | } 430 | 431 | void clear() noexcept { 432 | while (begin() != end()) pop_back(); 433 | } 434 | 435 | private: 436 | struct ImplStorage { 437 | void null() { 438 | begin_storage = end_storage = nullptr; 439 | begin_cursor = end_cursor = nullptr; 440 | } 441 | 442 | pointer begin_storage; // storage[0] 443 | pointer end_storage; // storage[n] (one-past-end) 444 | pointer begin_cursor; // devector[0] 445 | pointer end_cursor; // devector[n] (one-past-end) 446 | }; 447 | 448 | // Empty base class optimization. 449 | struct Impl : ImplStorage, Allocator { 450 | Impl() noexcept(std::is_nothrow_default_constructible::value) : Allocator() { } 451 | explicit Impl(const Allocator& alloc) noexcept : Allocator(alloc) { } 452 | explicit Impl(Allocator&& alloc) noexcept : Allocator(std::move(alloc)) { } 453 | 454 | Allocator& alloc() { return *this; } 455 | const Allocator& alloc() const { return *this; } 456 | 457 | Allocator& storage() { return *this; } 458 | const Allocator& storage() const { return *this; } 459 | } impl; 460 | 461 | // Deallocates the stored memory. Does not leave the devector in a valid state! 462 | void deallocate() noexcept { 463 | alloc_traits::deallocate(impl, impl.begin_storage, capacity()); 464 | } 465 | 466 | // Deletes all elements and deallocates memory. Does not leave the devector in a valid state. 467 | void destruct() noexcept { 468 | clear(); 469 | deallocate(); 470 | } 471 | 472 | // Reallocate with exactly space_front free space in the front, and space_back in the back. 473 | void reallocate(size_type space_front, size_type space_back) { 474 | // TODO It's possible that the user chose values such that the total new capacity needed is 475 | // smaller than capacity(). In that case we should not request a new memory chunk from the 476 | // allocator. 477 | 478 | size_type alloc_size = space_front + size() + space_back; 479 | 480 | pointer new_storage = alloc_traits::allocate(impl, alloc_size); 481 | pointer new_begin_cursor = new_storage + space_front; 482 | 483 | try { 484 | alloc_uninitialized_copy(detail::make_move_if_noexcept_iterator(begin()), 485 | detail::make_move_if_noexcept_iterator(end()), 486 | new_begin_cursor); 487 | } catch (...) { alloc_traits::deallocate(impl, new_storage, alloc_size); throw; } 488 | 489 | destruct(); 490 | impl.begin_storage = new_storage; 491 | impl.end_storage = new_storage + alloc_size; 492 | impl.begin_cursor = new_begin_cursor; 493 | impl.end_cursor = impl.end_storage - space_back; 494 | } 495 | 496 | 497 | // Make sure there is space for at least n elements at the front of the devector. This may steal 498 | // space from the back. 499 | void assure_space_front(size_type n) { 500 | if (impl.begin_cursor - impl.begin_storage >= difference_type(n)) return; 501 | 502 | // Don't compute this multiple times. 503 | size_type cap = capacity(); 504 | size_type sz = size(); 505 | 506 | size_type space_back = (impl.end_storage - impl.end_cursor) / 2; 507 | size_type sz_req = sz + n; 508 | size_type space_front_req = sz_req >= 16 ? sz_req / 3 : sz_req; 509 | size_type mem_req = sz_req + space_front_req + space_back; 510 | 511 | if (mem_req > cap) { 512 | // Use exponential growth with factor 1.5 (2 for small sizes) if possible. 513 | size_type alloc_size = cap * (3 + (cap < 16)) / 2; 514 | if (mem_req > alloc_size) reallocate(space_front_req, space_back); 515 | else reallocate(alloc_size - sz - space_back, space_back); 516 | } else { 517 | // We have enough space already, we just have to move elements around. 518 | pointer new_end_cursor = impl.end_storage - space_back; 519 | size_type num_move = std::min(new_end_cursor - impl.end_cursor, sz); 520 | 521 | // We now have to move the elements into their new location. Some of the new 522 | // locations are in uninitialized memory. This has to be handled seperately. 523 | alloc_uninitialized_copy(detail::make_move_if_noexcept_iterator(end() - num_move), 524 | detail::make_move_if_noexcept_iterator(end()), 525 | new_end_cursor - num_move); 526 | 527 | // Now move the rest. 528 | std::copy_backward(detail::make_move_if_noexcept_iterator(begin()), 529 | detail::make_move_if_noexcept_iterator(end() - num_move), 530 | end()); 531 | 532 | // Update cursors and destruct the values at the old beginning. 533 | while (num_move--) pop_front(); 534 | impl.begin_cursor = new_end_cursor - sz; 535 | impl.end_cursor = new_end_cursor; 536 | } 537 | } 538 | 539 | 540 | // Make sure there is space for at least n elements at the back of the devector. This may steal 541 | // space from the front. 542 | void assure_space_back(size_type n) { 543 | if (impl.end_storage - impl.end_cursor >= difference_type(n)) return; 544 | 545 | // Don't compute this multiple times. 546 | size_type cap = capacity(); 547 | size_type sz = size(); 548 | 549 | size_type sz_req = size() + n; 550 | size_type space_front = (impl.begin_cursor - impl.begin_storage) / 2; 551 | size_type space_back_req = sz_req >= 16 ? sz_req / 3 : sz_req; 552 | size_type mem_req = sz_req + space_front + space_back_req; 553 | 554 | if (mem_req > cap) { 555 | // Use exponential growth with factor 1.5 (2 for small sizes) if possible. 556 | size_type alloc_size = cap * (3 + (cap < 16)) / 2; 557 | if (mem_req > alloc_size) reallocate(space_front, space_back_req); 558 | else reallocate(space_front, alloc_size - sz - space_front); 559 | } else { 560 | // We have enough space already, we just have to move elements around. 561 | pointer new_begin_cursor = impl.begin_storage + space_front; 562 | size_type num_move = 563 | std::min(impl.begin_cursor - new_begin_cursor, sz); 564 | 565 | // We now have to move the elements into their new location. Some of the new 566 | // locations are in uninitialized memory. This has to be handled seperately. 567 | alloc_uninitialized_copy(detail::make_move_if_noexcept_iterator(begin()), 568 | detail::make_move_if_noexcept_iterator(begin() + num_move), 569 | new_begin_cursor); 570 | 571 | // Now move the rest. 572 | std::copy(detail::make_move_if_noexcept_iterator(begin() + num_move), 573 | detail::make_move_if_noexcept_iterator(end()), 574 | new_begin_cursor + num_move); 575 | 576 | // Update cursors and destruct the values at the old beginning. 577 | while (num_move--) pop_back(); 578 | impl.begin_cursor = new_begin_cursor; 579 | impl.end_cursor = new_begin_cursor + sz; 580 | } 581 | } 582 | 583 | // Fills [first, last) with constructed elements with args. Strong exception guarantee, cleans 584 | // up if an exception occurs. 585 | template 586 | pointer alloc_uninitialized_fill(pointer first, pointer last, Args&&... args) { 587 | pointer current = first; 588 | 589 | try { 590 | while (current != last) { 591 | alloc_traits::construct(impl, std::addressof(*current++), args...); 592 | } 593 | } catch (...) { 594 | while (first != current) alloc_traits::destroy(impl, std::addressof(*first++)); 595 | throw; 596 | } 597 | 598 | return current; 599 | } 600 | 601 | // Copies from the range [first, last) into the uninitialized range starting at d_first. Strong 602 | // exception guarantee, cleans up if an exception occurs. 603 | template 604 | pointer alloc_uninitialized_copy(InputIterator first, InputIterator last, pointer d_first) { 605 | pointer current = d_first; 606 | 607 | try { 608 | while (first != last) { 609 | alloc_traits::construct(impl, std::addressof(*current++), *first++); 610 | } 611 | } catch (...) { 612 | while (d_first != current) alloc_traits::destroy(impl, std::addressof(*d_first++)); 613 | throw; 614 | } 615 | 616 | return current; 617 | } 618 | 619 | // Initializes the devector with copies from [first, last). Strong exception guarantee. 620 | template 621 | void init_range(InputIterator first, InputIterator last, std::random_access_iterator_tag) { 622 | size_type n = last - first; 623 | 624 | if (n > 0) { 625 | impl.begin_storage = impl.begin_cursor = alloc_traits::allocate(impl, n); 626 | impl.end_storage = impl.end_cursor = impl.begin_storage + n; 627 | alloc_uninitialized_copy(first, last, impl.begin_cursor); 628 | } else { 629 | impl.null(); 630 | } 631 | } 632 | 633 | // Initializes the devector with copies from [first, last). Strong exception guarantee. 634 | template 635 | void init_range(InputIterator first, InputIterator last, std::bidirectional_iterator_tag) { 636 | impl.null(); 637 | while (first != last) push_back(*first++); 638 | } 639 | 640 | 641 | template 642 | void assign_range(InputIterator first, InputIterator last, std::random_access_iterator_tag) { 643 | size_type n = last - first; 644 | reserve(n); 645 | 646 | while (size() > n) pop_back(); 647 | for (auto& el : *this) el = *first++; 648 | while (first != last) push_back(*first++); 649 | } 650 | 651 | template 652 | void assign_range(InputIterator first, InputIterator last, std::bidirectional_iterator_tag) { 653 | auto it = begin(); 654 | while (it != end() && first != last) *it++ = *first++; 655 | while (it != end()) pop_back(); 656 | while (first != last) push_back(*first++); 657 | } 658 | 659 | // Helper functions for move assignment. Second argument is 660 | // alloc_traits::propagate_on_container_move_assignment::value. 661 | void move_assign_propagate_dispatcher(V&& other, std::true_type) noexcept { 662 | destruct(); 663 | impl.alloc() = std::move(other.impl.alloc()); 664 | impl.storage() = std::move(other.impl.storage()); 665 | other.impl.null(); 666 | } 667 | 668 | void move_assign_propagate_dispatcher(V&& other, std::false_type) { 669 | if (impl.alloc() != other.impl.alloc()) { 670 | destruct(); 671 | impl.null(); 672 | } 673 | 674 | assign(std::move_iterator(other.begin()), 675 | std::move_iterator(other.end())); 676 | 677 | other.destruct(); 678 | other.impl.null(); 679 | } 680 | 681 | // Helper functions for copy assignment. Second argument is 682 | // alloc_traits::propagate_on_container_copy_assignment::value. 683 | void copy_assign_propagate_dispatcher(const V& other, std::true_type) { 684 | if (impl.alloc() != other.impl.alloc()) { 685 | destruct(); 686 | impl.null(); 687 | } 688 | 689 | impl.alloc() = other.impl.alloc(); 690 | assign(other.begin(), other.end()); 691 | } 692 | 693 | void copy_assign_propagate_dispatcher(const V& other, std::false_type) { 694 | assign(other.begin(), other.end()); 695 | } 696 | 697 | template 698 | void resize_back_impl(size_type n, Args&&... args) { 699 | auto original_size = size(); 700 | 701 | reserve_back(n); 702 | while (n < size()) pop_back(); 703 | 704 | try { 705 | while (n > size()) emplace_back(args...); 706 | } catch (...) { 707 | while (size() > original_size) pop_back(); 708 | throw; 709 | } 710 | } 711 | 712 | template 713 | void resize_front_impl(size_type n, Args&&... args) { 714 | auto original_size = size(); 715 | 716 | reserve_back(n); 717 | while (n < size()) pop_front(); 718 | 719 | try { 720 | while (n > size()) emplace_front(args...); 721 | } catch (...) { 722 | while (size() > original_size) pop_front(); 723 | throw; 724 | } 725 | } 726 | }; 727 | 728 | 729 | // Comparison operators. 730 | template 731 | inline bool operator==(const devector& lhs, const devector& rhs) { 732 | return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); 733 | } 734 | 735 | template 736 | inline bool operator< (const devector& lhs, const devector& rhs) { 737 | return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 738 | } 739 | 740 | template 741 | inline bool operator!=(const devector& lhs, const devector& rhs) { 742 | return !(lhs == rhs); 743 | } 744 | 745 | template 746 | inline bool operator> (const devector& lhs, const devector& rhs) { 747 | return rhs < lhs; 748 | } 749 | 750 | template 751 | inline bool operator<=(const devector& lhs, const devector& rhs) { 752 | return !(rhs < lhs); 753 | } 754 | 755 | template 756 | inline bool operator>=(const devector& lhs, const devector& rhs) { 757 | return !(lhs < rhs); 758 | } 759 | 760 | template 761 | inline void swap(devector& lhs, devector& rhs) 762 | noexcept(noexcept(lhs.swap(rhs))) { 763 | lhs.swap(rhs); 764 | } 765 | 766 | /* 767 | Implementation notes. 768 | 769 | May throw exceptions: 770 | Allocator default construction. 771 | All value_type constructors. 772 | Allocation. 773 | 774 | May not throw exceptions mandated by the standard: 775 | Allocator copy/move construction. 776 | All alloc_traits::(const_)pointer operations. 777 | Deallocation. 778 | 779 | May throw exceptions but makes effects undefined: 780 | value_type destructor. 781 | 782 | Valid moved-from state: impl.null(). 783 | */ 784 | 785 | #endif 786 | -------------------------------------------------------------------------------- /docs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orlp/devector/7b99bbb0a760f46f8ce4e2bb3944e99700d78f41/docs.pdf -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Orson Peters 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In no event will the 4 | authors be held liable for any damages arising from the use of this software. 5 | 6 | Permission is granted to anyone to use this software for any purpose, including commercial 7 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 8 | 9 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the 10 | original software. If you use this software in a product, an acknowledgment in the product 11 | documentation would be appreciated but is not required. 12 | 13 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as 14 | being the original software. 15 | 16 | 3. This notice may not be removed or altered from any source distribution. 17 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Warning: `devector` is not finished yet and the implementation is currently an incomplete WIP. 2 | 3 | `devector` - a resizable contiguous sequence container with fast appends on either end 4 | ====================================================================================== 5 | 6 | Unlike `std::vector`, `devector` can have free capacity both before and after the elements. This 7 | enables efficient implementation of methods that modify the `devector` at the front. Anything a 8 | `std::vector` can do, a `devector` can as well. `devector`s available methods are a superset of 9 | those of `std::vector` with identical behaviour, barring a couple of iterator invalidation 10 | guarantees that differ. The overhead for `devector` is one extra pointer per container. 11 | `sizeof(devector) == 4*sizeof(T*)` as opposed to the general implementation of `sizeof(std::vector) 12 | == 3*sizeof(T*)`. Also, `devector` is not specialized. 13 | 14 | Whenever `devector` requires more free space at an end it applies the following allocation strategy: 15 | 16 | 1. Halve the amount of free space that will be left on the other end after the operation and 17 | calculate how much free space we want on this end after the operation. 18 | 19 | new_free_other = free_other / 2 20 | new_free_this = size() >= 16 ? size() / 3 : size() 21 | 22 | 2. If there isn't enough total memory to fit `new_free_other + size() + new_free_this`, then 23 | allocate more memory using an exponential factor of 1.5 with doubling for small sizes: 24 | 25 | new_mem = old_mem >= 16 ? old_mem * 1.5 : old_mem * 2 26 | 27 | 3. Move the elements into the appropriate memory location, leaving `new_free_other` and 28 | `new_free_this` free space at the ends. If new memory was allocated deallocate the old 29 | memory. 30 | 31 | Note that not every request for more space results in an reallocation. If half of the free space of 32 | the other side is enough to satisfy the needs of this side the elements are simply moved. 33 | 34 | Constantly halving the amount of free space on the side that it is not used on prevents wasted 35 | space. In the worst case where you push at one end and pop at the other (FIFO), memory is bounded to 36 | _5n/3_. This is because free space on the output end is constantly halved, but only `size() / 3` 37 | free space is required on the input end. 38 | 39 | Typedefs 40 | -------- 41 | 42 | typedef T value_type; 43 | typedef Allocator allocator_type; 44 | typedef typename alloc_traits::size_type size_type; 45 | typedef typename alloc_traits::difference_type difference_type; 46 | typedef typename alloc_traits::pointer pointer; 47 | typedef typename alloc_traits::const_pointer const_pointer; 48 | typedef T& reference; 49 | typedef const T& const_reference; 50 | typedef pointer iterator; 51 | typedef const_pointer const_iterator; 52 | typedef std::reverse_iterator reverse_iterator; 53 | typedef std::reverse_iterator const_reverse_iterator; 54 | 55 | All these typedefs are the same as for std::vector. 56 | 57 | Construction/Assignment/Swapping/Destructor 58 | ------------------------------------------- 59 | 60 | ~devector() noexcept; 61 | devector() noexcept(std::is_nothrow_default_constructible::value); 62 | explicit devector(const Allocator& alloc) noexcept; 63 | explicit devector(size_type n, const Allocator& alloc = Allocator()); 64 | devector(size_type n, const T& value, const Allocator& alloc = Allocator()); 65 | template 66 | devector(InputIterator first, InputIterator last, 67 | const Allocator& alloc = Allocator()); 68 | devector(const devector& other); 69 | devector(const devector& other, const Allocator& alloc); 70 | devector(devector&& other) noexcept; 71 | devector(devector&& other, const Allocator& alloc); 72 | devector(std::initializer_list il, const Allocator& alloc = Allocator()); 73 | devector& operator=(const devector& other); 74 | devector& operator=(devector&& other) noexcept(/* See below.. */); 75 | devector& operator=(std::initializer_list il); 76 | template 77 | void assign(InputIterator first, InputIterator last); 78 | void assign(size_type n, const T& t); 79 | void assign(std::initializer_list il); 80 | 81 | All these operations have the exact same syntax and semantics as std::vector. The move assignment 82 | operator is marked `noexcept` if the allocator should propagate on move assignment. 83 | 84 | Getters 85 | ------- 86 | 87 | allocator_type get_allocator() const noexcept; 88 | iterator begin() noexcept; 89 | const_iterator begin() const noexcept; 90 | iterator end() noexcept; 91 | const_iterator end() const noexcept; 92 | reverse_iterator rbegin() noexcept; 93 | const_reverse_iterator rbegin() const noexcept; 94 | reverse_iterator rend() noexcept; 95 | const_reverse_iterator rend() const noexcept; 96 | const_iterator cbegin() const noexcept; 97 | const_iterator cend() const noexcept; 98 | const_reverse_iterator crbegin() const noexcept; 99 | const_reverse_iterator crend() const noexcept; 100 | reference front() noexcept; 101 | const_reference front() const noexcept; 102 | reference back() noexcept; 103 | const_reference back() const noexcept; 104 | T* data() noexcept; 105 | const T* data() const noexcept; 106 | reference operator[](size_type i) noexcept; 107 | const_reference operator[](size_type i) const noexcept; 108 | reference at(size_type i); 109 | const_reference at(size_type i) const; 110 | size_type max_size() const noexcept; 111 | size_type size() const noexcept; 112 | bool empty() const noexcept; 113 | 114 | All these operations have the exact same syntax and semantics as `std::vector`. The only difference 115 | is that some functions have been marked `noexcept`, even though the standard doesn't require it. 116 | 117 | size_type capacity() const noexcept; 118 | size_type capacity_front() const noexcept; 119 | size_type capacity_back() const noexcept; 120 | 121 | The function `capacity` is an alias for `capacity_back`. `capacity_back` returns the number of 122 | elements in the container plus the amount of elements that can fit in the free space at the back. 123 | `capacity_front` does the exact same except for the free space at the front. This means that the 124 | total size of allocated memory is `sizeof(T) * (capacity_front() + capacity_back() - size())`. 125 | 126 | Modifiers 127 | --------- 128 | The following functions are also required to be `MoveAssignable` in addition to the limitations put 129 | forth by `std::vector`: `emplace_back`, `push_back`, `emplace_front`, `push_front`, `resize`, 130 | `resize_back`, `resize_front`. 131 | 132 | 133 | void swap(devector& other) noexcept(/* See below. */); 134 | void shrink_to_fit(); 135 | void clear() noexcept; 136 | template 137 | void emplace_back(Args&&... args); 138 | void push_back(const T& x); 139 | void push_back(T&& x); 140 | void pop_back(); 141 | 142 | All these operations have the exact same syntax and semantics as `std::vector`. The `noexcept` 143 | specifier for `swap` is only false if the allocator must be propagated on swap and it can not be 144 | swapped without exceptions. 145 | 146 | template 147 | void emplace_front(Args&&... args); 148 | void push_front(const T& x); 149 | void push_front(T&& x); 150 | 151 | Prepends an element to the container. If the new `size()` is greater than `capacity_front()` all 152 | references and iterators (including the past-the-end iterator) are invalidated. Otherwise all 153 | iterators and references remain valid. The behaviour on exceptions is the same as 154 | `push_back`/`emplace_back`. 155 | 156 | void pop_front(); 157 | 158 | Removes the first element of the container. Calling `pop_front` on an empty container is undefined. 159 | No iterators or references except `front()` and `begin()` are invalidated. 160 | 161 | void reserve(size_type n); 162 | void reserve(size_type new_front, size_type new_back); 163 | void reserve_front(size_type n); 164 | void reserve_back(size_type n); 165 | 166 | The single argument `reserve` is an alias for `reserve_back`. `reserve_back` has the exact same 167 | semantics as `std::vector`s `reserve`. `reserve_front` does the same as `reserve_back` except it 168 | influences `capacity_front()` rather than the capacity at the back. The two argument `reserve` has 169 | the same behaviour as two calls to respectively `reserve_front` and `reserve_back`, but is more 170 | efficient by doing at most one reallocation. 171 | 172 | void resize(size_type n); 173 | void resize(size_type n, const T& t); 174 | void resize_back(size_type n); 175 | void resize_back(size_type n, const T& t); 176 | void resize_front(size_type n); 177 | void resize_front(size_type n, const T& t); 178 | 179 | `resize` is an alias for `resize_back` and has the exact same semantics as `std::vector`. 180 | `resize_front` is the same as `resize_back` except that it resizes the container with 181 | `push_front`/`pop_front` rather than `push_back`/`pop_back`. 182 | 183 | template 184 | iterator emplace(const_iterator position, Args&&... args); 185 | iterator insert(const_iterator position, const T& t); 186 | iterator insert(const_iterator position, T&& t); 187 | iterator insert(const_iterator position, size_type n, const T& t); 188 | iterator insert(const_iterator position, std::initializer_list il); 189 | template 190 | iterator insert(const_iterator position, InputIterator first, InputIterator last); 191 | 192 | All these operations have the same semantics as `std::vector`, except for the iterators/references 193 | that get invalidated by these operations. If `position - begin() < size() / 2` then only the 194 | iterators/references after the insertion point remain valid (including the past-the-end iterator). 195 | Otherwise only the iterators/references before the insertion point remain valid. 196 | 197 | iterator erase(const_iterator position); 198 | 199 | Behaves the same as as `std::vector`, except for which iterators/references get invalidated. If 200 | `position - begin() < size() / 2` then all iterators and references at or before `position` are 201 | invalidated. Otherwise all iterators and references at or after (including `end()`) `position` are 202 | invalidated. 203 | 204 | iterator erase(const_iterator first, const_iterator last); 205 | 206 | Behaves the same as as `std::vector`, except for which iterators/references get invalidated. If 207 | `first - begin() < end() - last` then all iterators and references at or before `last` are 208 | invalidated. Otherwise all iterators and references at or after (including `end()`) `first` are 209 | invalidated. 210 | 211 | Lastly, `devector` has lexical comparison operator overloads and `swap` defined in its namespace 212 | just like `std::vector`. 213 | --------------------------------------------------------------------------------