Capstone Project 3

Linked List Library

Build a production-ready, STL-compatible linked list library in C++. Implement singly and doubly linked lists with full iterator support, memory pool optimization, move semantics, and comprehensive performance benchmarking. Master advanced C++ concepts including templates, RAII, and custom allocators.

12-16 hours
Advanced
500 Points
What You Will Build
  • STL-compatible linked list
  • Bidirectional iterators
  • Memory pool allocator
  • Move semantics support
  • Performance benchmarks
Contents
01

Project Overview

This advanced capstone project challenges you to build a professional-grade linked list library that matches the quality and performance of the C++ Standard Template Library (STL). You will work with the Algorithms and their Complexities dataset from Kaggle containing algorithm complexity data for testing insertion, deletion, traversal, and search operations across varying data sizes (100 to 1,000,000 elements). The dataset includes timing expectations, memory usage baselines, and comparison metrics against std::list, std::forward_list, and std::vector. Your library must demonstrate STL compatibility, memory efficiency, and performance optimization through custom allocators and cache-aware design.

Skills Applied: This project tests your proficiency in C++ templates (variadic, SFINAE), iterator design patterns, memory management (custom allocators, memory pools), move semantics, RAII principles, and performance analysis using big-O complexity and benchmarking tools.
Data Structure

Singly and doubly linked list implementations

Iterators

Forward, bidirectional, and const iterators

Memory Optimization

Custom allocators and memory pooling

Benchmarking

Performance testing and optimization

Learning Objectives

Technical Skills
  • Implement generic data structures with templates
  • Design STL-compatible iterators
  • Create custom memory allocators
  • Apply move semantics and perfect forwarding
  • Write exception-safe code with strong guarantees
Performance Skills
  • Analyze time and space complexity
  • Profile and benchmark code performance
  • Optimize for cache locality
  • Reduce memory fragmentation
  • Compare against STL implementations
Ready to submit? Already completed the project? Submit your work now!
Submit Now
02

Project Scenario

CoreLib Systems

You have been hired as a Systems Developer at CoreLib Systems, a company that develops high-performance C++ libraries for embedded systems and game engines. The company needs a custom linked list implementation that can be used in memory-constrained environments where the standard std::list may have too much overhead. The library must be fully STL-compatible so existing codebases can easily adopt it.

"We need a linked list library that's as easy to use as std::list but more memory-efficient and predictable in performance. It should work seamlessly with STL algorithms, support custom allocators for our memory pools, and give us control over cache behavior. Can you build a library that matches STL's interface while outperforming it in our specific use cases?"

Marcus Rodriguez, Lead Systems Architect

Functional Requirements

Core Operations
  • push_front, push_back, pop_front, pop_back
  • insert, erase, clear operations
  • front, back element access
  • size, empty, swap utilities
Iterator Support
  • begin(), end(), cbegin(), cend()
  • rbegin(), rend() for doubly linked
  • Forward iterator for singly linked
  • Bidirectional iterator for doubly linked
STL Compatibility
  • Work with std::find, std::sort algorithms
  • Range-based for loop support
  • Custom allocator template parameter
  • Type traits and concepts (C++20)
Advanced Features
  • splice, merge, reverse operations
  • unique, remove_if algorithms
  • Move constructor and assignment
  • emplace_front, emplace_back support
Pro Tip: Start with a basic singly linked list, then add iterator support, then upgrade to doubly linked. Finally, add the custom allocator and optimization layers. Test at each stage!
03

Benchmark Data

Use the comprehensive benchmark dataset to validate your linked list implementation's correctness and measure its performance against STL containers:

Dataset Download

Download the benchmark data files and operation sequences. Use these to test correctness and measure performance against baseline metrics.

Original Data Source

This project uses benchmark data inspired by the Algorithms and their Complexities dataset from Kaggle - containing algorithms with time/space complexity analysis and code implementations. The dataset includes complexity data for various data structure operations useful for performance testing.

Dataset Info: 10,000 operation sequences × 8 columns | Data sizes: 100 to 1,000,000 | Operations: insert, delete, traverse, search, splice, merge | Containers: forward_list, list, vector, deque | Metrics: time (μs), memory (bytes), cache misses
Dataset Schema

ColumnTypeDescription
test_idIntegerUnique test identifier (1-10000)
operationStringOperation type (push_front, push_back, insert, erase, traverse, find)
data_sizeIntegerNumber of elements (100, 1000, 10000, 100000, 1000000)
positionStringPosition indicator (front, back, middle, random)
expected_time_usFloatExpected execution time in microseconds
std_list_time_usFloatstd::list baseline time in microseconds
complexityStringExpected time complexity (O(1), O(n), O(log n))
categoryStringTest category (insertion, deletion, access, algorithm)

ColumnTypeDescription
sequence_idIntegerUnique sequence identifier (1-500)
operationsStringComma-separated operation sequence
initial_valuesStringInitial list values (semicolon-separated)
expected_resultStringExpected final list state
test_typeStringTest type (correctness, performance, stress)

ColumnTypeDescription
containerStringContainer type (std::list, std::forward_list, std::vector, std::deque)
operationStringOperation type
data_sizeIntegerNumber of elements
avg_time_usFloatAverage execution time (μs)
min_time_usFloatMinimum execution time (μs)
max_time_usFloatMaximum execution time (μs)
memory_bytesIntegerMemory usage in bytes
cache_missesIntegerL1 cache misses count

ColumnTypeDescription
element_typeStringData type (int, double, string, custom_struct)
element_countIntegerNumber of elements in list
std_list_bytesIntegerMemory usage by std::list
target_bytesIntegerTarget memory usage for your implementation
node_overheadIntegerPer-node memory overhead in bytes
allocator_typeStringAllocator type (default, pool, arena)
Dataset Stats: 10,000 benchmark tests, 500 operation sequences, 6 data sizes, 4 STL container baselines
Performance Targets: Match or beat std::list in 80% of benchmarks, reduce memory overhead by 20%
Sample Data Preview

Here is what typical benchmark records look like from linked_list_benchmark_data.csv:

test_idoperationdata_sizepositionexpected_time_usstd_list_time_uscomplexity
1push_front10000front0.150.18O(1)
2push_back10000back0.150.19O(1)
3insert10000middle25.428.7O(n)
4traverse100000full1250.01340.5O(n)
Performance Note: The benchmark includes cache-hostile access patterns intentionally. Your optimized implementation should demonstrate improvement through memory pooling and node placement strategies.
04

Project Requirements

Your linked list library must include all of the following components. Structure your implementation with clear separation between core data structure, iterators, and allocators.

1
Core Linked List Classes

Singly Linked List (forward_list):

  • Node structure with data and next pointer
  • push_front, pop_front - O(1) operations
  • insert_after, erase_after - positional operations
  • Forward iterator support only

Doubly Linked List (list):

  • Node structure with data, prev, and next pointers
  • push_front, push_back, pop_front, pop_back - O(1)
  • insert, erase at any position with iterator
  • Bidirectional iterator support
template<typename T, typename Allocator = std::allocator<T>>
class LinkedList {
public:
    // Type aliases for STL compatibility
    using value_type = T;
    using allocator_type = Allocator;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;
    using reference = value_type&;
    using const_reference = const value_type&;
    using pointer = typename std::allocator_traits<Allocator>::pointer;
    using const_pointer = typename std::allocator_traits<Allocator>::const_pointer;
    
    // Iterator types
    class iterator;
    class const_iterator;
    using reverse_iterator = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
    
    // Constructors, destructor, assignment
    LinkedList();
    explicit LinkedList(const Allocator& alloc);
    LinkedList(size_type count, const T& value, const Allocator& alloc = Allocator());
    LinkedList(std::initializer_list<T> init, const Allocator& alloc = Allocator());
    LinkedList(const LinkedList& other);
    LinkedList(LinkedList&& other) noexcept;
    ~LinkedList();
    
    LinkedList& operator=(const LinkedList& other);
    LinkedList& operator=(LinkedList&& other) noexcept;
    
    // Element access
    reference front();
    reference back();
    
    // Iterators
    iterator begin() noexcept;
    iterator end() noexcept;
    const_iterator cbegin() const noexcept;
    const_iterator cend() const noexcept;
    
    // Capacity
    bool empty() const noexcept;
    size_type size() const noexcept;
    
    // Modifiers
    void clear() noexcept;
    iterator insert(const_iterator pos, const T& value);
    iterator erase(const_iterator pos);
    void push_front(const T& value);
    void push_back(const T& value);
    void pop_front();
    void pop_back();
    
    // Emplace operations
    template<typename... Args>
    iterator emplace(const_iterator pos, Args&&... args);
    template<typename... Args>
    reference emplace_front(Args&&... args);
    template<typename... Args>
    reference emplace_back(Args&&... args);
    
private:
    struct Node {
        T data;
        Node* prev;
        Node* next;
        
        template<typename... Args>
        Node(Args&&... args) : data(std::forward<Args>(args)...), prev(nullptr), next(nullptr) {}
    };
    
    Node* head_;
    Node* tail_;
    size_type size_;
    Allocator allocator_;
};
Deliverable: Header files linked_list.hpp and forward_list.hpp with full template implementations.
2
Iterator Implementation

Requirements for STL-compatible iterators:

  • Forward iterator for singly linked list (InputIterator, OutputIterator, ForwardIterator)
  • Bidirectional iterator for doubly linked list (adds BidirectionalIterator)
  • Both const and non-const versions
  • Proper iterator_traits specialization
  • Support for range-based for loops
// Iterator class (nested in LinkedList)
class iterator {
public:
    using iterator_category = std::bidirectional_iterator_tag;
    using value_type = T;
    using difference_type = std::ptrdiff_t;
    using pointer = T*;
    using reference = T&;
    
    iterator() : node_(nullptr) {}
    explicit iterator(Node* node) : node_(node) {}
    
    reference operator*() const { return node_->data; }
    pointer operator->() const { return &(node_->data); }
    
    iterator& operator++() {
        node_ = node_->next;
        return *this;
    }
    
    iterator operator++(int) {
        iterator tmp = *this;
        ++(*this);
        return tmp;
    }
    
    iterator& operator--() {
        node_ = node_->prev;
        return *this;
    }
    
    iterator operator--(int) {
        iterator tmp = *this;
        --(*this);
        return tmp;
    }
    
    bool operator==(const iterator& other) const { return node_ == other.node_; }
    bool operator!=(const iterator& other) const { return node_ != other.node_; }
    
private:
    Node* node_;
    friend class LinkedList;
};
Deliverable: Iterators that pass STL algorithm compatibility tests (std::find, std::copy, std::distance, etc.).
3
List Operations

Implement the following list-specific operations:

  • splice: Transfer elements from another list without copying
  • merge: Merge two sorted lists into one
  • reverse: Reverse the order of elements in-place
  • unique: Remove consecutive duplicate elements
  • remove/remove_if: Remove elements matching value/predicate
  • sort: Sort elements (merge sort recommended)
// List operations
void splice(const_iterator pos, LinkedList& other);
void splice(const_iterator pos, LinkedList& other, const_iterator it);
void splice(const_iterator pos, LinkedList& other, const_iterator first, const_iterator last);

void merge(LinkedList& other);
template<typename Compare>
void merge(LinkedList& other, Compare comp);

void reverse() noexcept;

void unique();
template<typename BinaryPredicate>
void unique(BinaryPredicate p);

void remove(const T& value);
template<typename UnaryPredicate>
void remove_if(UnaryPredicate p);

void sort();
template<typename Compare>
void sort(Compare comp);
Deliverable: All operations implemented with optimal time complexity (splice O(1), merge O(n), sort O(n log n)).
4
Testing and Benchmarking

Unit Tests (using Google Test or Catch2):

  • Test all constructors and assignment operators
  • Test all modifiers with various data types
  • Test iterator operations and STL algorithm compatibility
  • Test exception safety (strong guarantee for modifiers)
  • Test memory correctness with valgrind/AddressSanitizer

Performance Benchmarks:

  • Benchmark push/pop operations against std::list
  • Benchmark traversal with various element counts
  • Benchmark splice/merge operations
  • Measure memory usage per element
Deliverable: Test suite with 90%+ code coverage and benchmark results comparing against std::list and std::forward_list.
05

STL Compatibility

Your linked list must be fully compatible with the C++ Standard Template Library. This means working seamlessly with STL algorithms, type traits, and idiomatic C++ patterns.

Algorithm Compatibility

Your list must work with these STL algorithms:

  • std::find, std::find_if - element search
  • std::count, std::count_if - element counting
  • std::copy, std::move - element transfer
  • std::for_each - apply function to each element
  • std::transform - transform elements
  • std::accumulate - reduce operations
  • std::distance - iterator distance
  • std::advance - iterator advancement
Type Traits Support

Implement proper type aliases and traits:

  • value_type - element type
  • allocator_type - allocator type
  • size_type - unsigned size type
  • difference_type - signed difference type
  • reference, const_reference
  • pointer, const_pointer
  • iterator, const_iterator
  • reverse_iterator, const_reverse_iterator
Usage Examples
// Range-based for loop
LinkedList<int> list = {1, 2, 3, 4, 5};
for (const auto& item : list) {
    std::cout << item << " ";
}

// STL algorithm usage
auto it = std::find(list.begin(), list.end(), 3);
if (it != list.end()) {
    list.erase(it);
}

// Transform with lambda
LinkedList<int> result;
std::transform(list.begin(), list.end(),
    std::back_inserter(result),
    [](int x) { return x * 2; });
// Initializer list construction
LinkedList<std::string> names = {"Alice", "Bob", "Carol"};

// Move semantics
LinkedList<int> source = {1, 2, 3};
LinkedList<int> dest = std::move(source);
// source is now empty, dest has elements

// Custom allocator
using PoolAlloc = MemoryPoolAllocator<int>;
LinkedList<int, PoolAlloc> pooled_list;

// Emplace for efficiency
struct Person { std::string name; int age; };
LinkedList<Person> people;
people.emplace_back("Alice", 30);
Compatibility Test: Your implementation passes if the following code compiles and runs correctly:
LinkedList<int> list = {3, 1, 4, 1, 5, 9, 2, 6};
list.sort();
list.unique();
auto sum = std::accumulate(list.begin(), list.end(), 0);
std::cout << "Sum of unique sorted elements: " << sum << std::endl;
06

Performance Optimization

Implement these optimization techniques to achieve performance that matches or exceeds the standard library implementation. Focus on memory efficiency and cache performance.

Optimization 1

Memory Pool Allocator

Reduce allocation overhead and fragmentation

Implement a custom memory pool allocator that pre-allocates blocks of nodes:

template<typename T, size_t BlockSize = 4096>
class MemoryPoolAllocator {
public:
    using value_type = T;
    using pointer = T*;
    using size_type = std::size_t;
    
    MemoryPoolAllocator() : current_block_(nullptr), current_slot_(nullptr), 
                            last_slot_(nullptr), free_slots_(nullptr) {}
    
    ~MemoryPoolAllocator() {
        // Free all allocated blocks
        slot_pointer_ curr = current_block_;
        while (curr != nullptr) {
            slot_pointer_ prev = curr->next;
            operator delete(reinterpret_cast<void*>(curr));
            curr = prev;
        }
    }
    
    pointer allocate(size_type n = 1) {
        if (free_slots_ != nullptr) {
            pointer result = reinterpret_cast<pointer>(free_slots_);
            free_slots_ = free_slots_->next;
            return result;
        }
        
        if (current_slot_ >= last_slot_) {
            allocate_block();
        }
        return reinterpret_cast<pointer>(current_slot_++);
    }
    
    void deallocate(pointer p, size_type n = 1) {
        if (p != nullptr) {
            reinterpret_cast<slot_*>(p)->next = free_slots_;
            free_slots_ = reinterpret_cast<slot_*>(p);
        }
    }
    
private:
    union slot_ {
        value_type element;
        slot_* next;
    };
    
    using data_pointer_ = char*;
    using slot_pointer_ = slot_*;
    
    slot_pointer_ current_block_;
    slot_pointer_ current_slot_;
    slot_pointer_ last_slot_;
    slot_pointer_ free_slots_;
    
    size_type padPointer(data_pointer_ p, size_type align) const {
        uintptr_t result = reinterpret_cast<uintptr_t>(p);
        return ((align - result) % align);
    }
    
    void allocate_block() {
        data_pointer_ new_block = reinterpret_cast<data_pointer_>(
            operator new(BlockSize));
        reinterpret_cast<slot_pointer_>(new_block)->next = current_block_;
        current_block_ = reinterpret_cast<slot_pointer_>(new_block);
        
        data_pointer_ body = new_block + sizeof(slot_pointer_);
        size_type body_padding = padPointer(body, alignof(slot_));
        current_slot_ = reinterpret_cast<slot_pointer_>(body + body_padding);
        last_slot_ = reinterpret_cast<slot_pointer_>(
            new_block + BlockSize - sizeof(slot_) + 1);
    }
};
Benefits
  • O(1) allocation and deallocation
  • Reduced memory fragmentation
  • Better cache locality within blocks
  • Fewer system calls (malloc/free)
Considerations
  • Higher initial memory usage
  • Block size tuning needed
  • Not suitable for variable-size objects
  • Thread safety requires locking
Optimization 2

Cache-Aware Design

Improve cache hit rates during traversal

Linked lists are inherently cache-unfriendly due to pointer chasing. Implement these techniques:

Unrolled Linked List

Store multiple elements per node to improve cache utilization:

template<typename T, size_t NodeCapacity = 16>
struct UnrolledNode {
    std::array<T, NodeCapacity> elements;
    size_t count;
    UnrolledNode* prev;
    UnrolledNode* next;
    
    // Elements stored contiguously
    // Better cache performance on traversal
};
Prefetching

Use compiler hints to prefetch next nodes:

void traverse() {
    Node* curr = head_;
    while (curr != nullptr) {
        // Prefetch next node while processing current
        if (curr->next != nullptr) {
            __builtin_prefetch(curr->next, 0, 1);
        }
        process(curr->data);
        curr = curr->next;
    }
}
Expected Improvement: Unrolled lists can achieve 2-4x faster traversal due to reduced cache misses and pointer dereferences.
Optimization 3

Move Semantics & Perfect Forwarding

Eliminate unnecessary copies

Implement efficient move operations and perfect forwarding for element construction:

// Move constructor - O(1), no element copies
LinkedList(LinkedList&& other) noexcept
    : head_(other.head_), tail_(other.tail_), size_(other.size_), 
      allocator_(std::move(other.allocator_)) {
    other.head_ = nullptr;
    other.tail_ = nullptr;
    other.size_ = 0;
}

// Move assignment operator
LinkedList& operator=(LinkedList&& other) noexcept {
    if (this != &other) {
        clear();  // Free existing nodes
        head_ = other.head_;
        tail_ = other.tail_;
        size_ = other.size_;
        allocator_ = std::move(other.allocator_);
        other.head_ = nullptr;
        other.tail_ = nullptr;
        other.size_ = 0;
    }
    return *this;
}

// Perfect forwarding with emplace
template<typename... Args>
reference emplace_back(Args&&... args) {
    Node* new_node = create_node(std::forward<Args>(args)...);
    if (tail_ == nullptr) {
        head_ = tail_ = new_node;
    } else {
        tail_->next = new_node;
        new_node->prev = tail_;
        tail_ = new_node;
    }
    ++size_;
    return new_node->data;
}

// Efficient splice - O(1), no copies
void splice(const_iterator pos, LinkedList& other) {
    if (other.empty()) return;
    
    Node* before = pos.node_->prev;
    Node* after = pos.node_;
    
    // Link other's head
    if (before != nullptr) {
        before->next = other.head_;
    } else {
        head_ = other.head_;
    }
    other.head_->prev = before;
    
    // Link other's tail
    other.tail_->next = after;
    if (after != nullptr) {
        after->prev = other.tail_;
    } else {
        tail_ = other.tail_;
    }
    
    size_ += other.size_;
    other.head_ = other.tail_ = nullptr;
    other.size_ = 0;
}
Performance Impact: Move operations for a list of 1M elements: ~1 nanosecond (just pointer swaps) vs copy operations: ~50 milliseconds (element-by-element copy).
Optimization 4

Performance Benchmarking

Measure and compare performance metrics

Use Google Benchmark or a custom benchmarking harness to measure performance:

#include <benchmark/benchmark.h>
#include "linked_list.hpp"
#include <list>

// Benchmark push_back operation
static void BM_CustomList_PushBack(benchmark::State& state) {
    for (auto _ : state) {
        LinkedList<int> list;
        for (int i = 0; i < state.range(0); ++i) {
            list.push_back(i);
        }
        benchmark::DoNotOptimize(list);
    }
    state.SetComplexityN(state.range(0));
}

static void BM_StdList_PushBack(benchmark::State& state) {
    for (auto _ : state) {
        std::list<int> list;
        for (int i = 0; i < state.range(0); ++i) {
            list.push_back(i);
        }
        benchmark::DoNotOptimize(list);
    }
    state.SetComplexityN(state.range(0));
}

// Benchmark traversal
static void BM_CustomList_Traverse(benchmark::State& state) {
    LinkedList<int> list;
    for (int i = 0; i < state.range(0); ++i) {
        list.push_back(i);
    }
    
    for (auto _ : state) {
        int sum = 0;
        for (const auto& item : list) {
            sum += item;
        }
        benchmark::DoNotOptimize(sum);
    }
    state.SetComplexityN(state.range(0));
}

BENCHMARK(BM_CustomList_PushBack)->Range(100, 1000000)->Complexity();
BENCHMARK(BM_StdList_PushBack)->Range(100, 1000000)->Complexity();
BENCHMARK(BM_CustomList_Traverse)->Range(100, 1000000)->Complexity();

BENCHMARK_MAIN();
Expected Benchmark Results
Operation Elements std::list (μs) Your List (μs) Target Improvement
push_back (each)10,0000.180.1233% faster
push_front (each)10,0000.190.1237% faster
full traversal100,0001,3401,10018% faster
splice (full list)10,0000.050.05match
sort10,0002,5002,20012% faster
Performance Optimization Tips
Memory Layout
  • Use memory pools for nodes
  • Align nodes to cache lines
  • Minimize node overhead
  • Consider unrolled lists
Code Efficiency
  • Use move semantics everywhere
  • Perfect forwarding for emplace
  • Inline small functions
  • Avoid virtual functions
Profiling Tools
  • perf for CPU profiling
  • valgrind --tool=cachegrind
  • Google Benchmark
  • AddressSanitizer
07

Code Structure

Organize your project with a clean, modular structure. Use header-only templates for the core library and separate compilation units for tests and benchmarks.

cpp-linked-list/
├── include/
│   ├── linked_list.hpp           # Doubly linked list implementation
│   ├── forward_list.hpp          # Singly linked list implementation
│   ├── iterator.hpp              # Iterator implementations
│   ├── allocator/
│   │   ├── pool_allocator.hpp    # Memory pool allocator
│   │   └── arena_allocator.hpp   # Arena allocator (bonus)
│   └── detail/
│       ├── node.hpp              # Node structures
│       └── traits.hpp            # Type traits helpers
├── src/
│   └── benchmark_main.cpp        # Benchmark entry point
├── tests/
│   ├── test_linked_list.cpp      # Doubly linked list tests
│   ├── test_forward_list.cpp     # Singly linked list tests
│   ├── test_iterator.cpp         # Iterator tests
│   ├── test_allocator.cpp        # Allocator tests
│   └── test_stl_compat.cpp       # STL algorithm compatibility tests
├── benchmarks/
│   ├── bench_operations.cpp      # Operation benchmarks
│   ├── bench_memory.cpp          # Memory usage benchmarks
│   └── bench_comparison.cpp      # Comparison with std::list
├── data/
│   └── (downloaded CSV files)    # Benchmark data files
├── docs/
│   ├── API.md                    # API documentation
│   ├── BENCHMARKS.md             # Benchmark results
│   └── DESIGN.md                 # Design decisions
├── CMakeLists.txt                # CMake build configuration
├── README.md                     # Project documentation
└── .gitignore
Class Hierarchy
Core Classes
LinkedList<T, Allocator>
├── Node (internal)
├── iterator
├── const_iterator
├── reverse_iterator
└── const_reverse_iterator

ForwardList<T, Allocator>
├── Node (internal)
├── iterator
└── const_iterator
Allocator Classes
std::allocator<T> (default)

MemoryPoolAllocator<T, BlockSize>
├── allocate(n)
├── deallocate(p, n)
├── construct(p, args...)
└── destroy(p)

ArenaAllocator<T> (bonus)
├── allocate(n)
├── deallocate(p, n) // no-op
└── reset()
08

Submission Requirements

Create a public GitHub repository with the exact name shown below:

Required Repository Name
cpp-linked-list
github.com/<your-username>/cpp-linked-list
Required Project Structure
cpp-linked-list/
├── include/
│   ├── linked_list.hpp           # Main doubly linked list header
│   ├── forward_list.hpp          # Singly linked list header
│   └── pool_allocator.hpp        # Memory pool allocator
├── tests/
│   ├── test_linked_list.cpp      # Unit tests
│   └── test_stl_compat.cpp       # STL compatibility tests
├── benchmarks/
│   └── benchmark.cpp             # Performance benchmarks
├── data/
│   └── benchmark_results.csv     # Your benchmark results
├── docs/
│   └── BENCHMARKS.md             # Benchmark analysis
├── CMakeLists.txt                # Build configuration
├── README.md                     # Project documentation
└── screenshots/
    ├── benchmark_chart.png       # Performance comparison chart
    └── memory_usage.png          # Memory usage visualization
README.md Required Sections
1. Project Header
  • Project title and description
  • Your full name and submission date
  • Course and project number
2. Features
  • List all implemented features
  • STL compatibility status
  • Optimization techniques used
3. Build Instructions
  • Prerequisites (CMake, compiler)
  • Build commands
  • Running tests and benchmarks
4. API Documentation
  • Constructor signatures
  • Method descriptions
  • Usage examples
5. Benchmark Results
  • Performance comparison tables
  • Charts/graphs embedded
  • Analysis of results
6. Design Decisions
  • Why certain optimizations
  • Trade-offs made
  • Alternative approaches considered
7. Testing
  • Test coverage summary
  • How to run tests
  • Known limitations
8. References
  • C++ reference links
  • Papers/articles referenced
  • Tools used
Do Include
  • Header-only library in include/
  • Comprehensive unit tests
  • Performance benchmarks with results
  • CMakeLists.txt for easy building
  • Memory pool allocator implementation
  • Benchmark charts in screenshots/
Do Not Include
  • Build artifacts (build/, cmake-build-*/)
  • IDE-specific files (.idea/, .vscode/)
  • Object files (*.o, *.obj)
  • Executable binaries
  • Large data files (use .gitignore)
Important: Before submitting, verify that your code compiles without warnings using -Wall -Wextra -Wpedantic and all tests pass. Run with AddressSanitizer to check for memory errors.
Submit Your Project

Enter your GitHub username - we will verify your repository automatically

09

Grading Rubric

Your project will be graded on the following criteria. Total: 500 points.

Criteria Points Description
Core Implementation 125 Singly and doubly linked list with all basic operations working correctly
Iterator Support 75 Forward, bidirectional, and const iterators with proper traits
STL Compatibility 75 Works with STL algorithms, range-based for, type traits
Performance Optimization 100 Memory pool allocator, move semantics, cache-aware design
Testing 50 Comprehensive unit tests with 90%+ coverage
Benchmarking 50 Performance comparison with std::list, documented results
Documentation 25 README quality, code comments, API docs
Total 500
Grading Levels
Excellent
450-500

Exceeds all requirements with exceptional quality

Good
375-449

Meets all requirements with good quality

Satisfactory
300-374

Meets minimum requirements

Needs Work
< 300

Missing key requirements

Ready to Submit?

Make sure you have completed all requirements and reviewed the grading rubric above.

Submit Your Project
10

Pre-Submission Checklist

Use this checklist to verify you have completed all requirements before submitting your project.

Core Implementation
Iterator Support
STL Compatibility
Performance
Final Check: Compile your code with g++ -std=c++17 -Wall -Wextra -Wpedantic and run with valgrind or AddressSanitizer to ensure no memory leaks or undefined behavior.