SoFunction
Updated on 2025-05-06

Detailed explanation of the usage scenarios of list in C++

1. Basic characteristics and implementation principles of list

1.1 The internal structure of list

In C++std::listIt is a sequence container implemented by a bidirectional linked list, and its core feature is discontinuous memory storage. Each element (node) contains three parts:

  • Data part (storage actual value)
  • Front-drive pointer (pointing to the previous node)
  • Successor pointer (points to the next node)
template <class T>
struct __list_node {
    __list_node* prev;
    __list_node* next;
    T data;
};

1.2 Comparison with other sequence containers

characteristic list vector deque
Memory layout Non-continuous continuous Segmented continuous
Random access O(n) O(1) O(1)
Head insertion/deletion O(1) O(n) O(1)
Tail insertion/deletion O(1) O(1) O(1)
Insert/delete in the middle O(1) O(n) O(n)
Iterator failed Only elements are deleted invalid Most operations fail Some operations fail
Memory overhead Higher (2 extra pointers per element) Low medium

2. The core usage scenarios of list

2.1 Frequent insertion/deletion operations at any location

Typical scenarios

  • Real-time data stream processing (such as transaction order modification)
  • Game object management (frequently add and delete game entities)
  • Buffer operations in text editor
// High frequency insertion and deletion examplestd::list&lt;Order&gt; order_book;

// Insert new order (O(1))auto it = order_book.begin();
std::advance(it, 100);  // Position to the 100th positionorder_book.insert(it, new_order);

// Delete specific orders (O(1))order_book.erase(found_order);

2.2 Scenarios where stable iterators are needed

Typical scenarios

  • Long-standing element references
  • Co-processing of multiple data structures (such as element exchange between multiple lists)
std::list&lt;Player&gt; game_players;
auto player_it = game_players.begin();

// The original iterator is still valid even if the element is inserted/deleted in another locationgame_players.push_back(Player());  // Does not affect player_itgame_players.pop_front();          // Does not affect player_it
// Unless the element pointed to by the iterator is deletedplayer_it = game_players.erase(player_it);  // Return to the next valid iterator

2.3 Large object storage avoids mobile overhead

Typical scenarios

  • Large data structures (such as matrices, image blocks)
  • Complex objects (polymorphic objects, large class instances)
class LargeObject {
    char data[1024*1024]; // 1MB data    // ...Other members};

std::list&lt;LargeObject&gt; big_data_store;

// Insert will not cause element movement (vector will cause reallocation and copying)big_data_store.push_back(LargeObject());

2.4 Scenarios that require special operations

List's unique efficient operation

  • splice():O(1) Time transfer element to another list
  • merge(): O(n) time merges two ordered lists
  • sort(): Special sorting algorithm for linked lists
std::list&lt;int&gt; list1 = {1, 3, 5};
std::list&lt;int&gt; list2 = {2, 4, 6};

// Merge two ordered lists(list2);  // list1 becomes {1,2,3,4,5,6}, list2 is empty
// Transfer elementsstd::list&lt;int&gt; list3 = {10, 20};
auto it = ();
std::advance(it, 2);
(it, list3);  // list1: {1,2,10,20,3,4,5,6}

3. Performance analysis and optimization

3.1 Memory access mode

Cache-unfriendly issues

// Continuous access to list elements (poor performance)std::list&lt;int&gt; nums(1000000);
int sum = 0;
for (int n : nums) {  // cache miss can be cache every time you visit    sum += n;
}

Optimization solution

  • Consider vector/deque for data that need frequent traversal
  • Package and store relevant data (improve locality)

3.2 Memory usage efficiency

Memory overhead calculation

// Under 64-bit systemsizeof(std::list&lt;int&gt;::node) == 
    sizeof(int) + 2*sizeof(void*) = 4 + 16 = 20byte(Usually aligned to24byte)

// Store 1 million ints:vectorMemory:~4MB
listMemory:~24MB

3.3 Iterator performance testing

#include <iostream>
#include <list>
#include <vector>
#include <chrono>

const int SIZE = 1000000;

void test_list_traversal() {
    std::list<int> lst(SIZE, 1);
    
    auto start = std::chrono::high_resolution_clock::now();
    long sum = 0;
    for (auto n : lst) sum += n;
    auto end = std::chrono::high_resolution_clock::now();
    
    std::cout << "list traversal: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() 
              << "ms\n";
}

void test_vector_traversal() {
    std::vector<int> vec(SIZE, 1);
    
    auto start = std::chrono::high_resolution_clock::now();
    long sum = 0;
    for (auto n : vec) sum += n;
    auto end = std::chrono::high_resolution_clock::now();
    
    std::cout << "vector traversal: " 
              << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() 
              << "ms\n";
}

int main() {
    test_list_traversal();
    test_vector_traversal();
    return 0;
}

Typical output

list traversal: 12ms
vector traversal: 2ms

4. Special application scenarios

4.1 LRU cache implementation

template <typename K, typename V>
class LRUCache {
    std::list<std::pair<K, V>> items;
    std::unordered_map<K, typename std::list<std::pair<K, V>>::iterator> key_map;
    size_t capacity;
    
public:
    LRUCache(size_t cap) : capacity(cap) {}
    
    void put(const K& key, const V& value) {
        auto it = key_map.find(key);
        if (it != key_map.end()) {
            (it->second);
            key_map.erase(it);
        }
        
        items.push_front({key, value});
        key_map[key] = ();
        
        if (() > capacity) {
            key_map.erase(().first);
            items.pop_back();
        }
    }
    
    bool get(const K& key, V& value) {
        auto it = key_map.find(key);
        if (it == key_map.end()) return false;
        
        ((), items, it->second);
        value = it->second->second;
        return true;
    }
};

4.2 Multi-level feedback queue scheduling

class Process {
    int pid;
    int remaining_time;
    // ...Other attributes};

class Scheduler {
    std::array&lt;std::list&lt;Process&gt;, 5&gt; queues; // 5 priority queues    
public:
    void add_process(Process&amp;&amp; p, int priority) {
        queues[priority].push_back(std::move(p));
    }
    
    void schedule() {
        for (auto&amp; queue : queues) {
            if (!()) {
                auto&amp; process = ();
                // Execute a time slice                process.remaining_time--;
                
                if (process.remaining_time &lt;= 0) {
                    queue.pop_front(); // Finish                } else if (&amp;queue != &amp;()) {
                    // Downgrade to the next priority                    queues[&amp;queue - &amp;queues[0] + 1].splice(
                        queues[&amp;queue - &amp;queues[0] + 1].end(),
                        queue, ());
                }
                break;
            }
        }
    }
};

V. Use Traps and Best Practices

5.1 Common Traps

Incorrectly using random access

std::list&lt;int&gt; lst = {1, 2, 3};
// Error: list does not support operator[]// int x = lst[1];  

// The correct way to do itauto it = ();
std::advance(it, 1);  // O(n) operationint x = *it;

Ignore memory overhead

// Storing small elements is extremely unpaidstd::list&lt;char&gt; char_list;  // Each char consumes 24+ bytes (64-bit system)

Inefficient search

std::list&lt;std::string&gt; names;
// O(n) lookup - poor performanceauto it = std::find((), (), "Alice");

5.2 Best Practices

Combined usage strategies

// Large object + frequent insertion and deletionstd::list&lt;Matrix&gt; matrix_pool;
// Small object + frequent traversalstd::vector&lt;int&gt; counters;

Pre-allocated nodes(From C++17):

std::list&lt;ExpensiveObject&gt; obj_list;
obj_list.emplace_back(args...);  // Directly construct to avoid copy/move

Using a custom allocator

template <typename T>
class PoolAllocator { /*...*/ };

std::list<int, PoolAllocator<int>> pooled_list;
  • Alternative considerations

    • Consider when random access is requiredstd::deque
    • C++17std::pmr::list(Polymorphic memory resource)
    • Invasive linked list (such as)

6. Summary and decision-making guide

6.1 When to choose list

Preferred list use

  • Need to frequently insert/delete elements in the middle of the sequence
  • Need a long-term stable iterator/pointer/reference
  • Store large or non-copyable/movable objects
  • Need to be executedsplicemergeSpecial operations for linked lists
  • Memory allocation requires fine control (combined with a custom allocator)

Avoid using list

  • Need to access the elements frequently randomly
  • Storage of small basic types (int, char, etc.)
  • Extremely limited memory resources
  • Requires optimal cache utilization
  • Need to interact with the C API (continuous memory required)

6.2 Alternatives to modern C++

  1. forward_list(C++11):

    • One-way link table
    • Smaller memory overhead (one pointer per node)
    • Lack of reverse traversal capability
  2. pmr::list(C++17):

#include <memory_resource>
std::pmr::list<int> pmr_list{std::pmr::monotonic_buffer_resource()};
    • Provide richer implementation of linked lists
    • likeboost::container::stable_vector

By deeply understanding the characteristics and applicable scenarios of list, developers can better use this classic data structure in appropriate occasions, balance performance and functional requirements, and write more efficient C++ code.

The above is the detailed explanation of the usage scenarios of list in C++. For more information about the usage scenarios of C++ list, please pay attention to my other related articles!