1. Basic characteristics and implementation principles of list
1.1 The internal structure of list
In C++std::list
It 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<Order> 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<Player> 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<LargeObject> 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<int> list1 = {1, 3, 5}; std::list<int> list2 = {2, 4, 6}; // Merge two ordered lists(list2); // list1 becomes {1,2,3,4,5,6}, list2 is empty // Transfer elementsstd::list<int> 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<int> 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<int>::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<std::list<Process>, 5> queues; // 5 priority queues public: void add_process(Process&& p, int priority) { queues[priority].push_back(std::move(p)); } void schedule() { for (auto& queue : queues) { if (!()) { auto& process = (); // Execute a time slice process.remaining_time--; if (process.remaining_time <= 0) { queue.pop_front(); // Finish } else if (&queue != &()) { // Downgrade to the next priority queues[&queue - &queues[0] + 1].splice( queues[&queue - &queues[0] + 1].end(), queue, ()); } break; } } } };
V. Use Traps and Best Practices
5.1 Common Traps
Incorrectly using random access:
std::list<int> 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<char> char_list; // Each char consumes 24+ bytes (64-bit system)
Inefficient search:
std::list<std::string> names; // O(n) lookup - poor performanceauto it = std::find((), (), "Alice");
5.2 Best Practices
Combined usage strategies:
// Large object + frequent insertion and deletionstd::list<Matrix> matrix_pool; // Small object + frequent traversalstd::vector<int> counters;
Pre-allocated nodes(From C++17):
std::list<ExpensiveObject> 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 required
std::deque
- C++17
std::pmr::list
(Polymorphic memory resource) - Invasive linked list (such as)
- Consider when random access is required
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 executed
splice
、merge
Special 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++
-
forward_list(C++11):
- One-way link table
- Smaller memory overhead (one pointer per node)
- Lack of reverse traversal capability
pmr::list(C++17):
#include <memory_resource> std::pmr::list<int> pmr_list{std::pmr::monotonic_buffer_resource()};
-
:
- Provide richer implementation of linked lists
- like
boost::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!