A high-performance, header-only C++17 chunked vector implementation with comprehensive testing and iterator debugging support.
- Header-only library - Easy to integrate into your projects
- C++17 compatible - Modern C++ with backward compatibility
- Cross-platform - Builds and runs on Windows, Linux, and macOS
- Comprehensive testing - Extensive test suite with GoogleTest
- Memory efficient - Chunked memory allocation strategy
- STL compatible - Works with standard algorithms and iterators
- Iterator debugging - Debug-level iterator validation similar to Microsoft STL
- Custom allocator support - Override memory allocation via macros
- Performance optimized - Optimized for power-of-2 page sizes and trivial types
Each badge above represents a different aspect of our continuous integration:
#include "chunked_vector/chunked_vector.h"
int main() {
dod::chunked_vector<int> vec;
// Add some elements
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
// Use like std::vector
for (const auto& item : vec) {
std::cout << item << " ";
}
return 0;
}The chunked_vector is a high-performance container that provides a std::vector-like interface while using a chunked memory allocation strategy. Instead of storing all elements in a single contiguous memory block, it organizes data into fixed-size pages (chunks), providing several advantages:
- Reduced memory fragmentation - Allocates memory in fixed-size chunks
- Stable element addresses - Elements don't move during growth (except during explicit operations)
- Efficient growth - No need to copy entire contents when expanding
- Cache-friendly access - Elements within a page are contiguous
- Configurable page size - Tune for specific use cases
- Iterator debugging - Comprehensive iterator validation in debug builds
- O(1) worst-case push_back() - Unlike std::vector's O(1) amortized (no element copying during growth)
- Iterator stability - push_back() never invalidates existing iterators
- Predictable performance - No performance spikes from reallocation
- No element movement - Existing elements never move during growth
- Reduced fragmentation - Fixed-size page allocation
- Stable addresses - Element addresses remain constant
- Efficient allocation - Only allocate what you need, when you need it
// std::vector - potentially dangerous
std::vector<int> std_vec = {1, 2, 3};
auto it = std_vec.begin();
std_vec.push_back(4); // May invalidate 'it' if reallocation occurs
// *it; // Potentially undefined behavior
// chunked_vector - always safe
chunked_vector<int> chunked_vec = {1, 2, 3};
auto it = chunked_vec.begin();
chunked_vec.push_back(4); // Never invalidates 'it'
*it; // Always safe - returns 1template <typename T, size_t PAGE_SIZE = 1024>
class chunked_vector;T- The type of elements stored in the vectorPAGE_SIZE- Number of elements per page (default: 1024)- Must be greater than 0
- Power-of-2 values are optimized for better performance using bit operations
- Recommended values: 256, 512, 1024, 2048, 4096
using value_type = T;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using iterator = basic_iterator<T>;
using const_iterator = basic_iterator<const T>;// Default constructor
chunked_vector() noexcept;
// Fill constructor
explicit chunked_vector(size_type count);
chunked_vector(size_type count, const T& value);
// Initializer list constructor
chunked_vector(std::initializer_list<T> init);
// Copy constructor
chunked_vector(const chunked_vector& other);
// Move constructor
chunked_vector(chunked_vector&& other) noexcept;// Unchecked access
reference operator[](size_type pos);
const_reference operator[](size_type pos) const;
// Checked access (throws std::out_of_range)
reference at(size_type pos);
const_reference at(size_type pos) const;
// First and last elements
reference front();
const_reference front() const;
reference back();
const_reference back() const;iterator begin() noexcept;
const_iterator begin() const noexcept;
const_iterator cbegin() const noexcept;
iterator end() noexcept;
const_iterator end() const noexcept;
const_iterator cend() const noexcept;bool empty() const noexcept;
size_type size() const noexcept;
size_type capacity() const noexcept;
size_type max_size() const noexcept;
void reserve(size_type new_capacity);
void shrink_to_fit();
static constexpr size_t page_size();void clear() noexcept;
void push_back(const T& value);
void push_back(T&& value);
template<typename... Args>
reference emplace_back(Args&&... args);
void pop_back();
void resize(size_type count);
void resize(size_type count, const T& value);
// Erase operations
iterator erase(const_iterator pos);
iterator erase(const_iterator first, const_iterator last);
iterator erase_unsorted(const_iterator pos); // Fast unordered erasechunked_vector& operator=(const chunked_vector& other);
chunked_vector& operator=(chunked_vector&& other) noexcept;
chunked_vector& operator=(std::initializer_list<T> init);#include "chunked_vector/chunked_vector.h"
#include <iostream>
int main() {
// Create a chunked_vector with default page size
dod::chunked_vector<int> vec;
// Add elements
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
// Access elements
std::cout << "First: " << vec.front() << std::endl;
std::cout << "Last: " << vec.back() << std::endl;
std::cout << "Size: " << vec.size() << std::endl;
// Iterate
for (const auto& value : vec) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}// Use smaller page size for memory-constrained environments
dod::chunked_vector<int, 256> small_vec;
// Use larger page size for better cache performance
dod::chunked_vector<int, 4096> large_vec;
// Power-of-2 page sizes are optimized
dod::chunked_vector<int, 1024> optimized_vec;struct Point {
float x, y, z;
Point(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z) {}
Point(const Point&) = default;
Point& operator=(const Point&) = default;
Point(Point&&) = default;
Point& operator=(Point&&) = default;
};
int main() {
dod::chunked_vector<Point> points;
// Add points using different methods
points.push_back({1.0f, 2.0f, 3.0f});
points.emplace_back(4.0f, 5.0f, 6.0f);
// Reserve space to avoid reallocations
points.reserve(1000);
// Resize with default-constructed elements
points.resize(10);
// Resize with specific value
points.resize(20, Point{1.0f, 1.0f, 1.0f});
return 0;
}#include <algorithm>
#include <numeric>
int main() {
dod::chunked_vector<int> vec = {5, 2, 8, 1, 9, 3};
// Sort elements
std::sort(vec.begin(), vec.end());
// Find elements
auto it = std::find(vec.begin(), vec.end(), 8);
if (it != vec.end()) {
std::cout << "Found 8 at position " << std::distance(vec.begin(), it) << std::endl;
}
// Calculate sum
int sum = std::accumulate(vec.begin(), vec.end(), 0);
std::cout << "Sum: " << sum << std::endl;
return 0;
}int main() {
dod::chunked_vector<int> vec;
// Reserve capacity to avoid multiple reallocations
vec.reserve(10000);
// Use emplace_back for in-place construction
for (int i = 0; i < 10000; ++i) {
vec.emplace_back(i);
}
// Use erase_unsorted for fast removal when order doesn't matter
auto it = vec.begin() + 100;
vec.erase_unsorted(it); // O(1) instead of O(n)
// Shrink to fit to reclaim unused memory
vec.shrink_to_fit();
return 0;
}| Operation | Time Complexity | Notes |
|---|---|---|
operator[], at() |
O(1) | Optimized for power-of-2 page sizes |
front(), back() |
O(1) | Direct access |
push_back(), emplace_back() |
O(1) | May allocate new page |
pop_back() |
O(1) | No reallocation |
erase() |
O(n) | Elements need to be shifted |
erase_unsorted() |
O(1) | Fast unordered removal |
clear() |
O(n) for non-trivial types, O(1) for trivial | Destructor calls |
resize() |
O(k) where k is the difference | Construction/destruction |
| Iterator increment | O(1) | Cached page access |
- Memory overhead:
sizeof(T*) * number_of_pages + small_constant - Memory efficiency: ~99% for large containers (overhead becomes negligible)
- Fragmentation: Minimal due to fixed-size page allocation
- Trivial types: Optimized bulk operations using
memcpyandmemset - Power-of-2 page sizes: Bit operations instead of division/modulo
- Iterator caching: Reduces page lookup overhead during iteration
- Geometric growth: Page array grows similar to
std::vector
The library includes comprehensive iterator debugging support similar to Microsoft STL's _ITERATOR_DEBUG_LEVEL:
// Enable iterator debugging (default in debug builds)
#define CHUNKED_VEC_ITERATOR_DEBUG_LEVEL 1
#include "chunked_vector/chunked_vector.h"
// Disable iterator debugging for maximum performance
#define CHUNKED_VEC_ITERATOR_DEBUG_LEVEL 0
#include "chunked_vector/chunked_vector.h"See ITERATOR_DEBUG_README.md for detailed information.
// Define custom allocator macros before including the header
#define CHUNKED_VEC_ALLOC(size, alignment) my_aligned_alloc(size, alignment)
#define CHUNKED_VEC_FREE(ptr) my_free(ptr)
#include "chunked_vector/chunked_vector.h"// Define custom assertion macro
#define CHUNKED_VEC_ASSERT(expr) my_assert(expr)
#include "chunked_vector/chunked_vector.h"// Override inlining behavior
#define CHUNKED_VEC_INLINE __forceinline
#include "chunked_vector/chunked_vector.h"- Initial state: No pages allocated
- Growth: Pages allocated on-demand during
push_back()orreserve() - Page array growth: Geometric growth (1.5x) similar to
std::vector - Alignment: Respects type alignment requirements (minimum
alignof(void*))
Page Array: [Page0*] [Page1*] [Page2*] [Page3*] ...
| | | |
v v v v
Pages: [elem0...] [elem1024...] [elem2048...] [elem3072...]
- Windows: Uses
_mm_malloc()and_mm_free()for aligned allocation - POSIX: Uses
aligned_alloc()andfree() - macOS: Ensures minimum alignment of
alignof(void*)for compatibility
-
Choose appropriate page size:
- Smaller for memory-constrained environments
- Larger for better cache performance
- Power-of-2 values for optimal performance
-
Reserve capacity: Use
reserve()when the final size is known -
Use
emplace_back(): More efficient thanpush_back()for complex types -
Consider
erase_unsorted(): Much faster when element order doesn't matter -
Enable iterator debugging: Use in development builds to catch iterator bugs
-
Profile your use case: Test different page sizes to find the optimal value
- CMake 3.14 or higher
- C++17 compatible compiler
- Git (for GoogleTest dependency)
# Clone the repository
git clone https://github.com/SergeyMakeev/ChunkedVector.git
cd ChunkedVector
# Configure and build
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
# Run tests
cd build
ctest --output-on-failure# Add the chunked_vector library
add_subdirectory("path_to_chunked_vector_repo/chunked_vector")
# Link to your target
target_link_libraries(my_app_name PRIVATE chunked_vector)The project includes scripts to test iterator debugging at different levels:
test_iterator_debug_levels.cmdchmod +x test_iterator_debug_levels.sh
./test_iterator_debug_levels.shThis project is licensed under the terms specified in the LICENSE file.