Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intrusive lists

Tags:

c++

c

People also ask

What are intrusive lists?

An intrusive list is one where the pointer to the next list node is stored in the same structure as the node data. This is normally A Bad Thing, as it ties the data to the specific list implementation.

What is intrusive data structure?

An intrusive container is a data structure that can be used to hold a collection of objects where the bookkeeping used to track membership in the container is stored in the objects themselves instead of holding the object alongside of the bookkeeping in a structure.

Why is an intrusive list used in Linux kernel?

Intrusive linked lists force the data structures in the list to contain list pointers inside of the actual data structure, and the list operations manipulate those list-specific pointers in the data structure.

What differentiates a circular linked list from a normal linked list?

The normal linked list has the last node with a null pointer but a circular linked list always points to the head of the linked list means that the linked list is strat with the head and in the end, it again points to the head. As the name indicates that the circular linked lists and a circle have no ends.


An intrusive list is one where the pointer to the next list node is stored in the same structure as the node data. This is normally A Bad Thing, as it ties the data to the specific list implementation. Most class libraries (for example, the C++ Standard Library) use non-intrusive lists, where the data knows nothing about the list (or other container) implementation.


I actually like the intrusive model.

  1. It's better on memory (not many small allocations for things to point at other things)
  2. It allows you to have an object that exist in multiple containers at once.
  3. It allows you to find an element with one search mode (hash) but then find the next element in lexographic order
    • This is not the same as #2, but it can be accomplished with boost's multi_index_container, but note that multi_index_container has certain shortcomings that are non-issues with intrusive containers.

Intrusive is GOOD

...you just need to know what you are doing (which is true for any container).


It surprising how so many people get this completely wrong (such as the answer from Ziezi). People seem to over-complicate things when it's really pretty simple.

In an intrusive linked list there is no explicit 'Node' struct/class. Instead the 'Data' struct/class itself contains a next and prev pointer/reference to other Data in the linked list.

For example (intrusive linked list node):

struct Data { 
   Data *next; 
   Data *prev; 
   int fieldA; 
   char * fieldB; 
   float fieldC; 
}  

Notice how the next and prev pointers sit alongside and intrude on the private data fields of the entity such as fieldA. This 'violates' the separation of concerns enforced by standard linked lists (see below) but has benefits in greatly reducing the amount of list walking to locate specific nodes as well as lower memory allocations.

In an intrusive linked list, the 'linked list' itself is often virtual, there is normally no need to create a linked list struct/class at all.

Instead you can simply store a head pointer to the first Data item in some owner/manager. This manager also contains Add/Remove functions to update pointers as needed. For more info see https://gameprogrammingpatterns.com/spatial-partition.html

Having a single pair of next/prev pointers dictates that each object can only belong to one list. However you can of course add multiple pairs of next/prev pointers as needed (or define an array of next/prev pointers) to support objects in multiple lists.

In a non-intrusive (ie standard) linked list the next/prev pointers are part of a dedicated 'node' entity and the actual Data entity simply a field in that node.

For example (non intrusive linked list node and data):

struct Data { 
   int fieldA; 
   char * fieldB; 
   float fieldC; 
}  

struct Node { 
   Node *next; 
   Node *prev; 
   Data *data; 
}  

Notice how the next/prev pointers do not intrude on the actual Data entity and the separation of concerns is maintained.

Update:

You may see other sites such as https://www.data-structures-in-practice.com/intrusive-linked-lists/ use a 'List' struct (actually a Node) that contains next/prev pointers and is the single intrusive field in the 'Data' struct/class.

This does hide the next/prev pointers from the Data, however it suffers from the need to perform pointer arithmetic simply to access the actual Data associated with the List (Node).

This approach adds needless complexity in my option (over simply embedding next/prev fields directly) just for the the dubious goal of hiding the next/prev pointers. If you need intrusive lists, keep them simple as possible. (Also, in managed memory languages it is difficult or impossible to do pointer arithmetic anyway.)


Here is a brief description that is valid for lists as well:

I. Intrusive containers.

Object to be stored contains additional information to allow integration in container. Example:

struct Node
{
    Node* next;   // additional
    Node* prev;   // information 
    T data;
} 

1. Pros:

  • stores the objects themselves.

  • doesn't involve memory management.

  • iteration is faster.
  • better exception guarantees.
  • predictability in insertion and deletion of objects. (no additional (non-predictable) memory management is required.)
  • better memory locality.

2. Cons:

  • contains additional data for container integration. (every store type must be adapted (modified) to the container requirements.)
  • caution with possible side effects when changing the contents of the stored object.(especially for associative containers.)
  • lifetime management of the inserted object, independently from the container.
  • object can be possibly disposed before erased from the container leading to iterator invalidation.
  • intrusive containers are NON-copyable and NON-assignable.

II. Non-instrusive containers (C++ standard containers)

Object doesn't "know" and contain details about the container in which is to be stored. Example:

struct Node
{
    T data;
}

1. Pros:

  • does not containe additional information regarding the container integration.
  • object's lifetime managed by the container. (less complex.)

2. Cons:

  • store copies of values passed by the user. (inplace construction possible.)
  • an object can belong only to one container. (or the contaier should store pointers to objects.)
  • overhead on storing copies. (bookkeeping on each allocation.)
  • non-copyable or non-movable objects CAN'T be stored in non-intrusive containers.
  • can't store derived object and still maintain its original type. (slicing - looses polymorphism.)