A modern, C++17 compatible intrusive doubly-linked list implementation. This is a single-header, header-only library that provides efficient intrusive containers with sentinel circular list optimization.
# Clone or download the intrusive_list.hpp file
# Include it in your project
#include "intrusive_list/intrusive_list.hpp"
# Or build the complete tests
mkdir build && cd build
cmake ..
cmake --build .
ctest # Run all tests- C++17 Compatible: Uses modern C++ features like
autotemplate parameters - Header-Only: Single file implementation - just include
intrusive_list.hpp - STL-Compatible: Provides standard iterators and container interface
- Move Semantics: Full support for move constructors and move assignment
- Type Safety: Compile-time checks ensure correct usage
- Automatic Cleanup: Objects are automatically unlinked when destroyed
- Multiple Lists: Objects can be members of multiple lists simultaneously
- High Performance: Sentinel circular list eliminates redundant null checks
- Cross-Platform: Tested on Windows (MSVC), Linux (GCC), and macOS (Clang)
- Comprehensive Tests: 20+ unit tests covering all functionality
- CMake Integration: Modern CMake build system with automatic dependency management
#include "intrusive_list/intrusive_list.hpp"
// 1. Define your class with a list_node member
struct MyObject {
int value;
dod::list_node link; // The intrusive link
MyObject(int v) : value(v) {}
};
// 2. Create a list specifying the link member pointer
dod::intrusive_list<&MyObject::link> my_list;
// 3. Use like a standard container
MyObject obj1(42);
MyObject obj2(84);
my_list.push_back(obj1);
my_list.push_back(obj2);
// 4. Iterate using range-based for loops or iterators
for (const auto& obj : my_list) {
std::cout << obj.value << std::endl;
}- Standard containers: Own the objects (copy/move them in)
- Intrusive containers: Don't own objects, just link existing objects
- Standard containers: Allocate memory for elements
- Intrusive containers: No dynamic allocation, use existing object memory
// Standard container (owns objects)
std::list<MyObject> std_list;
std_list.emplace_back(42); // Creates object inside the container
// Intrusive container (links existing objects)
dod::intrusive_list<&MyObject::link> int_list;
MyObject obj(42); // Object exists independently
int_list.push_back(obj); // Links the existing objectpush_front(obj)- Add object to frontpush_back(obj)- Add object to backpop_front()- Remove first objectpop_back()- Remove last objectinsert(pos, obj)- Insert object before positionerase(pos)- Remove object at positionerase(obj)- Remove specific objectclear()- Remove all objectsempty()- Check if list is emptyswap(other)- Swap with another list
front()- Get reference to first objectback()- Get reference to last object
begin(),end()- Get iteratorscbegin(),cend()- Get const iterators
can_insert(obj)- Check if object can be safely insertednode_to_object(node)- Convert list_node pointer to object pointer (public utility)
Objects can be members of multiple lists by having multiple list_node members:
struct Employee {
std::string name;
dod::list_node dept_link; // For department list
dod::list_node project_link; // For project list
};
// Same employee can be in both lists
dod::intrusive_list<&Employee::dept_link> engineering;
dod::intrusive_list<&Employee::project_link> project_alpha;
Employee alice("Alice");
engineering.push_back(alice); // Alice is in engineering dept
project_alpha.push_back(alice); // Alice also works on project alphaThis implementation uses several optimization techniques:
- No null pointer checks: Sentinels eliminate branches in hot paths
- Simplified edge cases: Empty and non-empty lists handled uniformly
- Self-unlinking nodes: Nodes can remove themselves without list reference
- Single condition in
is_linked(): Only checks one pointer since both are always null or non-null together - Direct pointer arithmetic:
front()andback()avoid iterator overhead - Template consolidation: Single
node_to_object()handles both const and non-const cases - Eliminated redundant checks: Move operations and unlink avoid unnecessary null checks
- Objects must outlive the list: The list doesn't manage object lifetime
- No duplicate insertion: An object can only be in one instance of a specific list type at a time
- Automatic unlinking: Objects are automatically removed from lists when destroyed
- Debug assertions: Strategic assertions catch programming errors in debug builds
- Cache-friendly: No pointer chasing to separate allocations
- No allocations: Zero dynamic memory allocation
- Constant time operations: All operations are O(1) except clear()
- Minimal branches: Optimized for modern CPU branch prediction
- Not thread-safe: Like standard containers, requires external synchronization for concurrent access
# Linux/macOS with GCC
g++ -std=c++17 -Wall -Wextra your_code.cpp -o your_program
# Linux/macOS with Clang
clang++ -std=c++17 -Wall -Wextra your_code.cpp -o your_program
# Windows with MSVC (Visual Studio Developer Command Prompt)
cl /std:c++17 /EHsc your_code.cpp- C++17 or later: Uses modern C++ features
- CMake 3.14+: For building tests (uses FetchContent for Google Test)
- Compiler Support: GCC 7+, Clang 5+, MSVC 2017+
The implementation includes a comprehensive test suite with 20+ test cases covering:
- Basic Operations: Construction, insertion, removal, iteration
- Move Semantics: Move constructors and assignment operators
- Multiple Lists: Objects in multiple lists simultaneously
- Edge Cases: Empty lists, single elements, automatic cleanup
- Stress Testing: Performance with 1000+ objects
- Iterator Safety: Const and non-const iterator behavior
- Memory Safety: Automatic unlinking on destruction
- Node Move Semantics: Moving nodes between list positions
All tests pass on Windows (MSVC), Linux (GCC), and macOS (Clang).
| Operation | Time Complexity | Space Complexity |
|---|---|---|
push_front() |
O(1) | O(1) |
push_back() |
O(1) | O(1) |
pop_front() |
O(1) | O(1) |
pop_back() |
O(1) | O(1) |
insert() |
O(1) | O(1) |
erase() |
O(1) | O(1) |
clear() |
O(n) | O(1) |
front() |
O(1) | O(1) |
back() |
O(1) | O(1) |
| Iteration | O(n) | O(1) |
Memory overhead: Only 2 pointers per object (16 bytes on 64-bit systems) Cache performance: Excellent due to no additional allocations Branch prediction: Optimized to minimize conditional branches
This implementation prioritizes:
- Performance: Maximum speed through sentinel circular list optimization
- Safety: Compile-time checks and runtime assertions for debug builds
- Simplicity: Clean, readable code with minimal complexity
- Modern C++: Uses C++17 features for type safety and performance
The implementation uses a sentinel circular doubly-linked list design that eliminates most null pointer checks and provides optimal performance for intrusive container operations.
intrusive_list/intrusive_list.hpp- Main header-only implementationtest_intrusive_list.cpp- Comprehensive unit test suite (20 tests)intrusive_list/CMakeLists.txt- CMake configuration for the libraryCMakeLists.txt- Root CMake build configuration with Google Test integrationREADME.md- This documentation fileUSAGE.md- Additional usage examples and patternsLICENSE- License file
# Create build directory
mkdir build && cd build
# Configure with CMake
cmake ..
# Build everything
cmake --build . --config Release
# Run tests
ctest
# Or run tests directly
./Release/test_intrusive_list # Linux/macOS
.\Release\test_intrusive_list.exe # WindowsIf you prefer to compile manually, the library is header-only:
#include "intrusive_list/intrusive_list.hpp"
// Your code here - no linking requiredstruct Item {
int data;
dod::list_node link;
};
dod::intrusive_list<&Item::link> items;
Item item1{42};
items.push_back(item1);class ManagedItem {
dod::list_node link_;
public:
~ManagedItem() {
// Automatically unlinked from any lists
}
};// Range-based for (recommended)
for (auto& item : my_list) {
process(item);
}
// Direct iteration for maximum performance
for (auto it = my_list.begin(); it != my_list.end(); ++it) {
process(*it);
}