RList is a linked list in C for storing arbitrary, unmodified data structures as list payload by reference.
RList highlights:
- fully documented
- easy to use
- API is small but powerful
- API hides implementation details
- API encourages performance-conscious programming
- the unit-test code contains many examples of how to use the RList API
- depends only on assert(), malloc(), free() and the type
bool
- reliable
- covered by runtime consistency checks
- covered by unit tests
- used in other projects
RList favors ease-of-use and API-cleanliness over the highest possible performance. Thus, list operations require a few more memory references than highly optimized implementations and its data structures require slightly more memory. However within these constraints, RList is designed and implemented with performance and efficiency in mind.
- Storing data
- RList stores payload data by reference, i.e., via pointers. This has two implications:
- The data structure of the payload does not need to be modified to contain next pointers or the like.
- The memory management for payload objects is the responsibility of the user. A list is typically used in one of two scenarios:
- In a primary data container, the payload objects are to be de-allocated once they are removed from the container (e.g., a list of 'track' objects in a music collection).
- In a secondary data container, the lifetime of payload objects is independent of their membership in the container (e.g., a list of favorites referring to the 'track' objects in the primary list in music collection). Even though RList stores payload by reference and may seem better suited for secondary data containers, it conveniently supports both scenarios.
- Iterator-based API
- In RList, most list operations (adding, deleting, iterating over nodes) are based on iterators. An iterator stands for a particular node in a particular list. This makes RList easier to use: the interface is more consistent across different types of lists, internal details are hidden from the user, and the RList implementation can be more easily modified without requiring API changes. Another side effect is that deleting nodes in singly-linked lists via iterators is a very efficient O(1) operation.
- Performance-conscious API
- The RList API implements almost exclusively functions with a constant runtime complexity. The intention is to make it obvious when a list operation is expensive: you need to implement it yourself. For each API function the runtime complexity is documented.
- Robust API
- Almost all public RList functions are compatible with each other and can be mixed and matched. In particular, list nodes may be deleted while iterating over a list. It is clearly documented which use of the API is safe or unsafe.
- Public API vs. implementation internals
- All elements of the public API are visible in this documentation generated by doxygen. Everything that does not show up in the doxygen documentation can be considered internal to the RList implementation. These internals are also marked by their names ending in an underscore. Some parts of the implementation internals need to be present in the public header files for inlining. Do not use them as they might change or disappear across versions.
- Defensive implementation and consistency checks
- Every function in RList is covered by runtime consistency checks which are wrapped in assert() calls. These consistency checks help to verify that the RList implementation itself behaves as documented. At the same time, they can help to identify invalid or unsafe uses of the RList API by applications. However, they are only useful to developers as they provide the usual assert() style debug output and terminate the application. For production code, it is recommended to disable these consistency checks by compiling RList and the applications using it with the
NDEBUG
preprocessor macro which disables calls to assert().