Intrusive lists
I've not been able to find too much information about them online. What are they and when are they typically used?
Thanks.
Solution 1:
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.
Solution 2:
I actually like the intrusive model.
- It's better on memory (not many small allocations for things to point at other things)
- It allows you to have an object that exist in multiple containers at once.
- 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 thatmulti_index_container
has certain shortcomings that are non-issues with intrusive containers.
- This is not the same as #2, but it can be accomplished with boost's
Intrusive is GOOD
...you just need to know what you are doing (which is true for any container).
Solution 3:
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.)
Solution 4:
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.)
Solution 5:
Intrusives lists are lists where objects are themselves heads or cells of lists. They are good or bad things depending of context.
Inside some defined module (unsecable group of classes working together) it may be the BEST mean to tie relationships between classes. They allow no-cost direct and full management of common relationships like unicity (ex: apples does not apears two times in appletrees, and this does not need any key for this, and apples does not belong to two distincts trees), they are navigable in both directions (direct accès to appletree given an apple and to apples given some appletree). All basic operations are O(1) (no search in some external container).
Intrusive list are VERY BAD between two Modules. Because they will be tied together, and modules justification is management of code independance.