PLF Library - stack (original) (raw)
- Intro
- Implementation
- License
- Download
- Function Descriptions and Syntax
- Benchmarks
- Frequently-asked Questions
- Version History
- Contact
Intro
plf::stack is a container that gives the programmer the functionality of a FILO (first-in, last-out) stack. It is a more efficient drop-in replacement for std::stack, and faster than all std:: containers in the context of a stack. Like plf::colony it never invalidates references/pointers to non-popped elements, making it at least potentially useful for programming with containers of inter-related data structures. It supports C++11 move and emplace semantics and operations and has the following averaged performance characteristics versus std::stack:
- 82% faster for 1 byte types
- 83% faster for 4 byte types
- 80% faster for 8 byte types
- 82% faster for 40 byte types
- 724% faster for 490 byte types (note: libstdc++ basically turns a deque into a linked list at this point due to their 512-byte limit on block capacities)
Averaged across total numbers of stored elements ranging between 10 and 1000000. The benchmark in question is total time taken to construct, push all elements, read and pop all elements, then destruct. See benchmarks for more info.
Implementation
plf::stack uses the same chained-group memory allocation pattern as plf::colony ie. it is made up of a linked chain of 'groups', structs containing a memory block and metadata, with a growth factor of 2 for the memory blocks. The metadata within the groups - 'elements' (pointer to the memory block), previous_group (pointer or NULL), next_group (pointer or NULL), and 'end' (pointer to last element in memory block) - aid respectively in the identification of when when a pop reaches the start of the memory block, iterating backwards to the previous group, iterating forwards to the next group, and identifying when a push reaches the capacity of any given memory block.
When capacity of the current memory block is reached, a new group is created and linked to via the next_group pointer. plf::stack does not release memory blocks to the OS when the beginning of a group is reached via pop() and navigation to a previous group completed (this is deferred to the 'trim()' function, to be called by the programmer when convenient to the use-case). This avoids a deallocation/reallocation cost which could occur if the stack was pushed/popped repeatedly and group-boundaries crossed repeatedly in the process.
License
plf::stack is under a permissive zlib license. This means: Software is used on 'as-is' basis. It can be modified and used in commercial software. Authors are not liable for any damages arising from its use. The distribution of a modified version of the software is subject to the following restrictions:
- The authorship of the original software must not be misrepresented,
- Altered source versions must not be misrepresented as being the original software, and
- The license notice must not be removed from source distributions.
Download
Download here or view the repository.
plf::stack is a simple .h header-only library, to be used with an #include command.
If you are interested in benchmarking you can also download the plf benchmark suite.
Function Descriptions and syntax
For the most part the syntax and semantics of plf::stack functions are the same as std::stack. However there are a few key differences, such as the ability to specify min/max memory block sizes in the constructor. Formal description is as follows:
template <class T, class Allocator = std::allocator <T> > class stack
**T** - the element type. In general T must meet the requirements of Erasable, CopyAssignableand CopyConstructible.
However, if emplace is utilized to insert elements into the stack, and no functions which involve copying or moving are utilized, T is only required to meet the requirements of Erasable.
If move-insert is utilized instead of emplace, T must also meet the requirements of MoveConstructible.**Allocator** - an allocator that is used to acquire memory to store the elements. The type must meet the requirements of Allocator. The behavior is undefined if Allocator::value_type is not the same as T.
Basic example of usage
#include <iostream>
#include "plf_stack.h"
int main(int argc, char **argv)
{
plf::stack<int> i_stack;
// Insert 100 ints:
for (int i = 0; i != 100; ++i)
{
i_stack.push(i);
}
// Total the ints:
int total = 0;
while (!i_stack.empty())
{
total += i_stack.top();
i_stack.pop();
}
std::cout << "Total: " << total << std::endl;
std::cin.get();
return 0;
} Member types
| Member type | Definition |
|---|---|
| value_type | T |
| allocator_type | Allocator |
| size_type | Allocator::size_type (pre-c++11) std::allocator_traits::size_type (post-c++11) |
| reference | Allocator::reference (pre-c++11) value_type & (post-c++11) |
| const_reference | Allocator::const_reference (pre-c++11) const value_type & (post-c++11) |
| pointer | Allocator::pointer (pre-c++11) value_type & (post-c++11) |
| const_pointer | Allocator::const_pointer (pre-c++11) std::allocator_traits::const_pointer (post-c++11) |
Constructors
| default | explicit stack(const allocator_type &alloc = allocator_type()) explicit stack(const size_type min_group_size, const size_type max_group_size = std::numeric_limits<size_type>::max() / 2, const allocator_type &alloc = allocator_type()) |
|---|---|
| copy | stack(const stack &source) |
| move | stack(const stack &&source) noexcept (C++11 and upwards)Source stack has same status as an empty stack post-move. |
Constructor usage examples
stack<T> a_stack
Default constructor - default minimum group size is 8, default maximum group size isstd::numeric_limits<size_type>::max() / 2. The reason for this maximum limitation is that each new group doubles the number of existing elements in the stack. And since the maximum number of elements that can be possibly held in a stack is std::numeric_limits<size_type>::max(), the maximum group size must be half that.
Example:plf::stack<int> int_stack;stack<T, custom_allocator<T> > a_stack
Using a custom allocator.
Example:plf::stack<int, tbb::allocator<int> > int_stack;
Example2:// Using an instance of an allocator as well as it's type tbb::allocator<int> alloc_instance; plf::stack<int, tbb::allocator<int> > int_stack(alloc_instance);stack<T> a_stack(const size_type min_group_size)
This sets the minimum group size - for example, to 50 (unlike a vector, these 50 elements are not constructed at this point). This may yield a minor performance advantage if you know roughly how many elements are going to be stored in your stack in advance. Minimum group size may not be lower than 3.
Example:plf::stack<int> int_stack(62);stack<T> a_stack(const size_type min_group_size, const size_type max_group_size)
This sets the minimum group size as above, as well as the maximum group size - this can be useful if you want to limit memory usage. Minimum group size may not be lower than 3. Maximum group size may not be larger thanstd::numeric_limits<size_type>::max() / 2.
Example:plf::stack<int> int_stack(64, 512);stack<T> a_stack(const stack &source)
Copies all contents from source. New minimum group size is the total size ofsource, unlesssource's minimum group size is larger than it's total size, in which case the new minimum group size is the source's minimum group size. If source's total size is larger than it's maximum group size, the new minimum group size is the same as the source's maximum group size.
Example:plf::stack<int> int_stack_2(int_stack_1);stack<T> a_stack(stack && source) **C++11 and upwards**
Moves all contents from source, does not alter any group sizes. Source is now empty and can be used or destructed safely.
Example:plf::stack<int> int_stack_2(std::move(int_stack_1));
Member functions
void push(const the_type &element)
Push an element to the stack.
Example:object_stack.push(object1);void push(the_type &&element) **C++11 and upwards**
Move an element to the stack.
Example:object_stack.push(std::move(object1));void emplace(Arguments ...parameters) **C++11 and upwards**
Constructs an element directly on the stack using the the constructor arguments specified.
Example:object_stack.emplace(10, 200, "a");void pop()
Pop the last push()'d element off the stack. In debug mode will test for stack emptiness prior to operation via an assert. Calling pop() on an empty stack in non-debug mode results in undefined behaviour.
Example:object_stack.pop();the_type & top()
Return a reference to the last push()'d element on the stack. In debug mode will test for stack emptiness prior to operation via an assert. Calling top() on an empty stack in non-debug mode results in undefined behaviour.
Example:object2 = object_stack.top();size_type size()
Return the current number of elements stored on the stack.
Example:unsigned int j = static_cast<unsigned int>(object_stack.size());size_type max_size()
Returns the maximum number of elements that the allocator can store in the container. This is an approximation as it does attempt to measure the memory overhead of the container's internal memory structures. It is not possible to measure the latter because a copy operation may change the number of groups utilized for the same amount of elements, if the maximum or minimum group sizes are different in the source container.
Example:std::cout << i_stack.max_size() << std::endl;size_type capacity()
Returns total number of elements currently able to be stored in container without expansion.
Example:std::cout << i_stack.capacity() << std::endl;void reserve(size_type reserve_amount)
Preallocates memory space sufficient to store the number of elements indicated byreserve_amount. Will not change minimum and maximum group sizes. reserve_amount will be rounded up if it is lower than the minimum group size. By default the maximum group size is the maximum number allowed bysize_type.
Example:i_stack.reserve(15);void shrink_to_fit()
Reduces container capacity to the amount necessary to store all currently stored elements. If the total number of elements is larger than the maximum group size, the resultant capacity will be equal to((total_elements / max_group_size) + 1) * max_group_size. Invalidates all pointers, iterators and references to elements within the container.
Example:i_stack.shrink_to_fit();void trim()
Removes any trailing memory blocks within the container substructure, which may be present in the scenario of many pushes followed by many pops. Unlike a regular shrink_to_fit, does not shrink capacity to the size needed to contain the currently-stored elements, simply frees up unused blocks. Runtime cost is significantly lower than shrink_to_fit, but may not free as much memory.
Example:i_stack.trim();bool empty()
Returns a boolean indicating whether the stack is currently empty of elements.
Example:if (object_stack.empty()) return;void clear()
Empties the stack and removes all elements and groups.
Example:object_stack.clear();void reshape(const size_type min_group_size, const size_type max_group_size)
Changes the minimum and maximum internal group sizes, in terms of number of elements stored per group. If the stack is not empty and either min_group_size is larger than the smallest group in the stack, or max_group_size is smaller than the largest group in the stack, the stack will be internally copy-constructed into a new stack which uses the new group sizes, invalidating all pointers/iterators/references.
Example:object_stack.reshape(1000, 10000);void swap(stack &source)
Swaps the stack's contents with that ofsource.
Example:object_stack.swap(other_stack);friend void swap(stack &A, stack &B)
External friend function, swaps the stack A's contents with that of stack B (assumes both stacks have same element type).
Example:swap(object_stack, other_stack);stack & operator = (const stack &source)
Copy the elements from source stack to this stack, clearing this stack of existing elements first.
Example:object_stack = object_stack2;stack & operator = (const stack &&source) **C++11 and upwards**
Move the elements from source stack to this stack, clearing this stack of existing elements first. Source stack becomes empty and safely be reused or destructed.
Example:object_stack = std::move(object_stack2);bool operator == (const stack &source)
Compare contents of another stack to this stack. Returns a boolean to indicate whether they are equal.
Example:if (object_stack == object_stack2) return;bool operator != (const stack &source)
Compare contents of another stack to this stack. Returns a boolean to indicate whether they are not equal.
Example:if (object_stack != object_stack2) return;allocator_type get_allocator()
Returns a copy of the allocator used by this stackvoid append(stack &source)
Optimized function for appending thesourcestack's elements to the top of the stack. The source stack becomes empty post-append. This is much faster than performing sequential pushes while popping from the source stack. Internally the source stacks' groups get moved directly. Pointers to elements in the source stack are not guaranteed to be valid after the append. Pointers to elements in the destination stack remain valid.
Example:object_stack.append(other_stack);
Benchmarks
Tests are based on a sliding scale of number of runs vs number of elements, so a test with only 10 elements in a container may average 100000 runs, whereas a test with 100000 elements may only average 10 runs. This tends to give adequate results without overly lengthening test times.
Tests are carried out on the following types: (a) a 8-bit type ie. char, (b) a 32-bit type ie. int, (c) a 64-bit type ie. double, (d) a small struct containing two pointers and four scalar types, and (e) a large struct containing 2 pointers, 4 scalar types, a large array of ints and a small array of chars. In order to better facilitate accurate time-keeping for short tests, both container construction and destruction times are included in the tests. The sequence is as follows: construction, push N elements, read (back) + pop all elements, destruction. Because unlike a regular container, a stack must be pushed for every pop, and popped for every read, it most sense to combine these two timings and compare on that basis, as what is most important is the overall time taken. For that reason both separate and combined ("total time") benchmarks are presented below.
Benchmark results for stack v1.52 under GCC 9.2 x64 on an Intel Xeon E3-1241 (Haswell) are here.
Old benchmark results for stack v1.04 v3.87 under GCC 5.1 x64 on an Intel E8500 (Core2) are here,
and for stack v1.05 under MSVC 2015 update 3 on haswell here.
Version history
- 2025-07-11: v2.0.13 - Correction to type traits as C++26 deprecates std::is_trivial.
- 2025-02-28: v2.0.12 - Fix for move assignment when type has deleted move and/or copy constructor. Also minor optimization for move assignment under C++20 and above.
- 2025-02-09: v2.0.10 - Technical correction to copy construction under MSVC.
- 2024-11-04: v2.0.9 - const fix for iterators and move constructors/assignment.
- 2024-10-14: v2.0.8 - West const update - there was inconsistent use of const throughout due to my misunderstanding of how typedefs change const expession in C++.
- 2024-07-17: v2.0.7 - Fix to Stupid copy-paste bug in operator ==, removal of needless assert in same.
- 2024-07-17: v2.0.6 - Fix to support under GCC5 with -std=c++11, courtesy of Martin Valgur.
- 2024-04-21: v2.0.5 - Minor code reduction.
- 2024-04-10: v2.0.4 - Change to vcpkg-compatible versioning. plf::make_move_iterator changed to be static. plf::equal_to uses references now and is entirely noexcept.
- 2024-04-10: v2.0.3 - Fix cut-paste-bug.
- 2024-03-24: v2.02 - Minor optimisations and corrections. == and != operators changed to be friend functions.
- 2023-11-25: v2.01 - Swapped some manual swapping with std::swap where appropriate.
- 2023-08-10: v2.00 - Added iterators, for debugging purposes.
- 2023-07-05: v1.67 - Bypass buggy MSVC /W4 warning, comment corrections.
- 2023-06-23: v1.66 - Improvement for support on older compilers.
- 2023-02-03: v1.65 - More accurate compiler/library detection for C++20 support, C++20 support disabled for apple clang as it is not as up to date as non-apple clang.
- 2023-01-11: v1.64 - Support for disabling exceptions under GCC, Clang and MSVC. Will (a) now compile under gcc/clang when -fno-exceptions is specified and (b) will terminate in cases where exceptions would normally be thrown.
- 2022-10-10: v1.63 - Removed copy-paste glitch.
- 2022-10-02: v1.62 - Correction to move constructor. Correction for older compilers which partially support C++11 like MSVC2010. Removed plf::rand dependency from test suite.
- 2022-09-03: v1.61 - Correction for use with allocators which supply non-trivial pointers.
- 2022-08-27: v1.60 - Correction/reduction to compiler detection routines. Support for stateful allocators. Constructor corrections. Faulty <=> operator removed.
- 2021-12-11: v1.58 - Added spaceship operator for three-way comparison of stacks.
- 2021-09-18: v1.57 - Correction to compiler feature detections.
- 2021-05-26: v1.56 - Correction to compiler detection routines for clang/gcc running in MSVC environment.
- 2021-05-20: v1.55 - Correction to compiler detection routines for clang - only libc++ 13 and above support the concepts library.
- 2021-05-17: v1.54 - Corrections to copy constructor, operator = and shrink_to_fit for some edge cases. Additional tests added to test suite.
- 2021-05-16: v1.53 - Remove unneeded code from append().
- 2021-05-09: v1.52 - Minor code reduction, correction to splice (now throws if source memory blocks are outside destination's memory block capacity limits).
- 2021-05-07: v1.51 - Corrections to: reserve, copy constructors, reshape, shrink_to_fit. Code reduction and comment correction.
- 2021-05-06: v1.50 - Fix to copy/move constructor. Fixes to push and emplace. Numerous optimizations to core functions. Optimisation to shrink_to_fit. Fix for reserve when user has specified a max block size, also fix bug in reserve in general. Code reduction. Add reshape test to test suite and fix reshape.
- 2021-03-22: v1.49 - Minor code optimisation. Correction to MSVC compiler support for CPP20.
- 2021-01-13: v1.48 - Reduction of compiler feature macros for reasons of readability, code size and convenience of updating between the different containers. Hopefully, PLF_ by itself will be enough to disambiguate these from similarly-named macros. If not, will revert back. Improvement of a few compiler rules.
- 2021-01-11: v1.47 - Corrections to compiler feature detection.
- 2021-01-09: v1.46 - Correction to group allocations under C++98/03. Correction to header inclusion for length_error.
- 2021-01-03: v1.45 - plf::pcg_rand renamed to plf::rand and modified to enable better support for C++03 and lower compilers.
- 2020-12-26: v1.44 - Corrections to compiler feature detection code. Test suite now uses plf::pcg_rand instead of xor_rand. Correction to noexcept status on a constructor.
- 2020-11-06: v1.43 - free_unused_memory() renamed to trim(). Macro renaming and updating. set_block_capacity_limits() renamed to reshape(), set_minimum_block_capacity_limit() and set_maximum_block_capacity_limit() removed. Removal of unnecessary assertions. Incorrect min/max block limits now results in a throw(). [[nodiscard]] added to empty() under C++20.
- 2020-10-25: v1.42 - approximate_memory_use() renamed to memory().
- 2020-09-19: v1.41 - Correction to non-member swap, should've been in namespace std.
- 2020-06-27: v1.40 - Reduction of compiler switches.
- 2020-04-10: v1.39 - Fix accidental regression, add [[nodiscard]] for empty(). Correction to test suite.
- 2020-03-22: v1.38 - Updated noexcept conditions for swap and move operations.
- 2019-11-28: v1.37 - Adjustment to destruction meaning that under compilers supporting 'if constexpr', destruction should be slightly faster.
- 2019-11-18: v1.36 - Correction to shrink_to_fit.
- 2019-06-11: v1.35 - Correction to reserve() semantics. Reserve() also will no longer assert that reserve amount is within bounds of minimum group sizes, and will silently round the value up to minimum group size if necessary.
- 2019-04-15: v1.34 - Constexpr added to type traits if blocks and some other scenarios, for compilers which support it - including MSVC2017 with std:c++17 switch. This also creates performance benefits in some scenarios both in debug and release builds.
- 2019-04-12: v1.33 - shrink_to_fit(), reserve(), change_group_sizes(), change_minimum_group_size() and change_maximum_group_size() are now valid operations for element types which are move-constructible but not copy-constructible, when type traits are supported by the compiler. These operations will also be faster for non-trivially-copyable-but-trivially-movable types, if reallocation of existing elements is triggered.
- 2019-02-28: v1.32 - Added static_cast's to work around GCC's -Werror=sign-conversion warnings.
- 2019-01-16: v1.31 - Correction to library detection features to include type traits for libc++.
- 2018-11-16: v1.30 - Correction to max_size() for use with allocator_traits and to be in line with C++20.
- 2018-10-28: v1.29 - Optimization to move assignment - this also improves regular assignment, swap, append, change_group_sizes, shrink_to_fit, reserve.
- 2018-10-08: v1.28 - blank() no longer differentiates based on compiler (re-testing revealed this is best strategy across compilers). Correction: included header for offsetof.
- 2018-07-12: v1.27 - Optimization to swap for when allocator supplies non-trivial-but-movable pointers.
- 2018-07-07: v1.26 - Optimization to swap and some other functions. Updating of some CPU-specific tests. Comment correction/updates.
- 2018-07-06: v1.25 - Added void * casts to deal with GCC 8.1's garbage warning messages around memset.
- 2018-05-09: v1.24 - Mitigated NULL == 0 warning for clang, thanks to bluebear94.
- 2018-05-02: v1.23 - Same optimization for GCC & clear/splice/etc now applied to other compilers generically. Correction to macro usage.
- 2018-04-21: v1.22 - Code comment cleanup, minor optimisations to clear, move constructors/assignment. Postcondition of source colony for move-assignment/construction is the same as an empty colony now (ie. you can use it normally without undefined behaviours).
- 2018-04-13: v1.21 - Mistake in NOEXCEPT_MOVE_ASSIGNMENT compiler feature macro, fixed.
- 2018-04-02: v1.20 - Minor code cleanup/renaming. Corrections to compiler feature support detection. Corrections to some noexcept function statuses. Reversion of insert from a function call to emplace, back to a separate function - as this was causing copy-construction on non-trivial types, leading to additional temporaries and severe performance drop on large non-trivial types. Correction to move-push_back and move-push_front. Minor optimisation for C++03/98.
- 2017-11-01: v1.18 - Correction to no-exception compile-time checking for push/etc. Correction to shrink_to_fit(). Change to some inline hinting.
- 2017-10-11: v1.17 - Codegen reduction for push, move-push for compilers with variadic parameter support.
- 2017-09-13: v1.16 - Avoid generating exception try-catch code for nothrow-constructible types.
- 2017-08-31: v1.15 - Added "append" function - optimized transfer of the contents of one stack to the top of another.
- 2017-08-21: v1.12 - Warning removal for explicit constructor on EBCO variable. Changed name of 'trim_trailing_groups' to 'trim'.
- 2017-06-21: v1.11 - Correction to type trait support for non-GCC compilers. Correction to test suite for GCC >= v6.
- 2017-06-01: v1.10 - Correction to equality operator and missing header in test suite. Minor optimizations.
- 2017-05-21: v1.09 - Restored missing undef. Better support for non-GCC compilers.
- 2017-05-11: v1.08 - Minor corrections to default constructors, copy constructors and reserve. Emplace availability correction for some compilers. Minor optimization to C++03 swap. Other minor code improvements.
- 2017-05-06: v1.07 - Minor correction to noexcept statuses. Correction to test suite under C++03.
- 2017-03-28: v1.06 - Correction to emplace. Addition of allocator-extended move and copy constructors. Some standards compliance updates and corrections to noexcept statuses. Addition of get_allocator(). swap(stack &A, stack &B) is now non-member and non-friend.
- 2016-11-17: v1.05 - Improvement in performance for smaller numbers of scalar types.
- 2016-11-13: v1.04 - Minor update.
- 2016-10-15: v1.03 - Removed potential memory leak in clear, move and destructor.
- 2016-10-14: v1.02 - Corrections to move assignment operator, trim_trailing_groups, emplace, change_group_sizes and test_suite.
- 2016-10-10: v1.01 - change_group_size functions no longer copy-construct groups into a new stack unless the stack is not empty and either min_group_size is larger than the smallest group in the stack, or max_group_size is smaller than the largest group in the stack. All change_group_size functions now trim trailing groups. Emplace changed to no longer require move semantics, which enables the use of non-move-constructible/non-copy-constructible types with stack (example: anything containing a std::mutex).
- 2016-10-03: v1.00 - Separated from colony package, friendship removed. See colony history prior to v3.71 for earlier version changes.
Contact: 
plf:: library and this page Copyright (c) 2024, Matthew Bentley