Containers
This library provides various containers. Each container has utility functions to manipulate the data it holds. This is an abstraction as to not have to manually manage and reallocate memory.
|
For setup and compilation instructions visit the readme. For in-depth documentation, visit the code docs page.
Each container has an initialization function which returns a container object. For a deque, this would be deque_init()
which returns a deque
. The returned object is a pointer to an internal struct which contains information, but this is abstracted away to reduce mistakes and make development easier. More information about the initialization functions is presented below in its own section.
Once a container object is initialized, it is possible to manipulate it using the provided functions, which have in-depth documentation available. Each container has adding and retrieval type functions, and each type of container has its own specific set of function interfaces, which are explained in-depth at the function level in the code docs link above. More high-level information will be explained in its own section below.
Finally, each container will have to be destroyed to free the memory associated with it.
When creating a container, first you must decide what type of data you wish to store in it (or types for the case of a map). Then, you must either decide if you wish to store a copy of the data in the container, or a pointer to it. The benefit of a copy is that you don't have to manage the memory, and it is easier to reason. However, it might only be reasonable to store copies if the data type is relatively cheap to copy. Using pointers, you can either store references to automatic variables (make sure the lifetime stays valid while the data is stored), or dynamically-allocated variables (make sure to free at some point). Either way, you must pass in a pointer of what you wish to store, be it a pointer to a value, or a pointer to a pointer. Keep in mind that for some containers, changing data which is stored will cause undefined behavior. Fret not, as all containers document their potential undefined behavior in the function documentation comments.
Going back to the initialization function and ways we can initialize containers and and store data, we can use an example of a deque
:
Also, if the arguments which you passed in to the initialization function are invalid, or the system is out of memory, the initialization function may return NULL. Therefore, it is good to check for a return of NULL from the initialization functions.
Next, the two basic container manipulation functions of containers are to add and remove elements. To add elements, pass a pointer of the element you wish to copy to the add function. To remove an element, pass a pointer to a variable which can store the size of element that is stored in the container.
Using the deque
example, adding and removal can be done this way (if storing int):
Functions can fail for various reasons, such as the provided index argument being out of bounds, or the system running out of memory. The in-depth documentation linked above provides the exhaustive list of return codes for each function, which are present in the header file. For example, an invalid argument would return -BK_EINVAL
, and on success BK_OK
would be returned.
The associative containers and the priority queue require the user to initialize the container with a comparator, and the unordered associative containers also require a hash function to be passed in. State should not be modified in comparators or in hash functions, or else it would lead to undefined behavior.
When a comparator function is called, two arguments are passed in, being two elements to compare. The comparator must return 0 if they are equal, a negative value if the first is less than the second, and a positive value is the first is greater than the second. To be valid, a comparator must obey the following rules:
arg_1 == arg_1
and arg_2 == arg_2
must always be truearg_1 == arg_2
is true, then arg_2 == arg_1
is true(arg_1, arg_2)
and the second (arg_2, arg_3)
, then if arg_1 == arg_2
and arg_2 == arg_3
then arg_1 == arg_3
must be truearg_1 == arg_2
shall never change over time if both arguments do not changeWhen a hash function is called, it is provided one argument, and must hash its attributes. A hash is a mapping from the object to a number. Two objects which are equal must always result in the same hash being produced. The inverse is not required, but should be sufficiently improbable (meaning, two distinct objects may produce the same hash, but it should be very rare).
Using unordered set as an example (and storing int), the comparator and hash function can be passed in as follows when initializing:
A comparator can be as follows:
And a hash function can be as follows: