Hello,
I have a bunch of *.c files implementing various data structures
(lists, stacks, trees..). Till now, each time I had to use one of
these data structures I simply copied the relevant files and made the
required modifications.
I've noticed that lot of time is wasted converting the part handling
the specific objects types. (for example, the linked list
implementation holds structs of type `foo' while the working project
requires `bar'). The first thing that came to my mind was to use
something like this :
struct data_type {
element_type foo;
}
where element_type is a typedef one sets at compile time. Also, the
programmer defines a few functions for comparison/output of
element_type objects and there you go!
The problem I see with this solution is that given that program X uses
two linked lists one holding objects of type `foo' and some other
linked list of type `bar' I'd have to use two typedefs and have the
given code handling linked lists duplicated.
I'm therefore seeking for a better solution! Before I reinvent the
wheel I'd like to hear some suggestions from you!
You'll get a lot of suggestions from people recommending void*.
However, beware that you can use void* in two different ways: to hold
the data itself, or to *point* to the data.
The former solution is inferior, because (a) it can only handle
pointer types and (b) the object that the void* points to may be
deallocated (say if the object has automatic storage duration and the
program leaves the scope in which it's declared), and then the pointer
will become invalid.
To demonstrate the latter solution, here's an array ADT that uses it
(sorry if it's too long):
---------------------------------------
Header file
---------------------------------------
#ifndef ARRAY_H_
#define ARRAY_H_
#include <stdbool.h>
#include <stdlib.h>
struct _Array;
typedef struct _Array* Array;
typedef unsigned array_size_type;
typedef int array_index_type;
// The callback used to compare array elements; it should return a
strcmp()-style integer
typedef int (*ArrayElementsComparatorCallback)(const void*, const
void*);
// The callback used to destroy array elements; a function taking a
value of this type
// can be passed null, in which case no destructor will be called on
the elements
typedef void (*ArrayElementsDestructorCallback)(void*);
bool ArrayInit(Array*, array_size_type aElementSize);
bool ArrayInitWithArray(Array*, const Array aWithArray);
bool ArrayInitWithElements(Array*, array_size_type aElementSize, const
void* aElements, array_size_type aNumElements);
void ArrayDestroy(Array, ArrayElementsDestructorCallback
aDestructorCallback);
// @return the contiguous block of memory containing the elements
void* ArrayElements(Array);
const void* ArrayElements_const(const Array);
// @return the number of elements currently in the array
array_size_type ArraySize(const Array);
// @return the size of this array's elements
array_size_type ArrayElementSize(const Array);
inline bool ArrayIsEmpty(const Array array) {
return ArraySize(array) == 0;
}
// Searches for an item in the array and returns its index
// @param aItem the item to search for
// @param aStart the index to start from
// @param aComparatorCallback the callback used to compare elements
// @return the zero-based index of the element,
or -1 if not found
array_index_type ArrayIndexOf(const Array array, const void* aItem,
array_index_type aStart, ArrayElementsComparatorCallback
aComparatorCallback);
inline bool ArrayContains(const Array array, const void* aItem,
ArrayElementsComparatorCallback aComparatorCallback) {
return ArrayIndexOf(array, aItem, 0, aComparatorCallback) != -1;
}
// Replaces a range of elements with another array
// @param aStart the starting index of the elements to
replace
// @param aCount the number of elements to replace
// @param aItems the items to copy into the range
// @param aNumItems the number of elements in aItems
// @param aDestructorCallback a callback used to destroy the
replaced elements
// @return a pointer to the new elements
void* ArrayReplaceElementsAt(Array, array_index_type aStart,
array_size_type aCount, const void* aItems, array_size_type aNumItems,
ArrayElementsDestructorCallback aDestructorCallback);
// A variation on ArrayReplaceElementsAt
inline void* ArrayReplaceElementAt(Array array, array_index_type
aIndex, const void* aItem, ArrayElementsDestructorCallback
aDestructorCallback) {
return ArrayReplaceElementsAt(array, aIndex, 1, aItem, 1,
aDestructorCallback);
}
// Inserts an array at a specified position
// @param aIndex the index to insert the array at
// @param aItems the array to insert
// @param aNumItems the number of elements in aItems
// @return a pointer to the inserted elements
inline void* ArrayInsertElementsAt(Array array, array_index_type
aIndex, const void* aItems, array_size_type aNumItems) {
return ArrayReplaceElementsAt(array, aIndex, 0, aItems, aNumItems,
0);
}
// A variation on ArrayInsertElementsAt
inline void* ArrayInsertElementAt(Array array, array_index_type
aIndex, const void* aItem) {
return ArrayInsertElementsAt(array, aIndex, aItem, 1);
}
// Appends elements to the end of this array
// @param aItems the sequence of elements to append
// @param aNumItems the number of elements to append
// @return a pointer to the appended elements in the
array
void* ArrayAppendElements(Array, const void* aItems, array_size_type
aNumItems);
// A variation on ArrayAppendElements
inline void* ArrayAppendElement(Array array, const void* aItem) {
return ArrayAppendElements(array, aItem, 1);
}
// Removes a range of elements
// @param aStart the startig index of the elements to remove
// @param aCount the number of elements to remove
void ArrayRemoveElementsAt(Array, array_index_type aStart,
array_size_type aCount, ArrayElementsDestructorCallback
aDestructorCallback);
// A variation on ArrayRemoveElementsAt
inline void ArrayRemoveElementAt(Array array, array_index_type aIndex,
ArrayElementsDestructorCallback aDestructorCallback) {
ArrayRemoveElementsAt(array, aIndex, 1, aDestructorCallback);
}
// Searches for an element and removes it
// @param aItem the item to search for
// @param aComparatorCallback the callback used to compare elements
// @param aDestructorCallback the callback used to destroy the
element
// @return true if the element was found (and
therefore removed)
bool ArrayRemoveElement(Array array, const void* aItem,
ArrayElementsComparatorCallback aComparatorCallback,
ArrayElementsDestructorCallback aDestructorCallback);
// Removes all elements in the array
inline void ArrayClear(Array array, ArrayElementsDestructorCallback
aDestructorCallback) {
ArrayRemoveElementsAt(array, 0, ArraySize(array),
aDestructorCallback);
}
// Direct element access. These do NOT bounds-check
void* ArrayAt(Array, array_index_type aIndex);
const void* ArrayAt_const(const Array, array_index_type aIndex);
inline void ArraySort(Array array, ArrayElementsComparatorCallback
aComparatorCallback) {
qsort(ArrayElements(array), ArraySize(array), ArrayElementSize
(array), aComparatorCallback);
}
#endif /* ARRAY_H_ */
------------------------------------------
Source file
------------------------------------------
#include "Array.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
struct _Array
{
array_size_type capacity;
array_size_type size;
array_size_type elementSize;
void* contents;
};
#define kArrayIncrementSize 64
bool ArrayInit(Array* array,
array_size_type aElementSize)
{
return ArrayInitWithElements(array, aElementSize, 0, 0);
}
bool ArrayInitWithArray(Array* array,
const Array aWithArray)
{
return ArrayInitWithElements(array, aWithArray->elementSize,
aWithArray->contents, aWithArray->size);
}
bool ArrayInitWithElements(Array* array,
array_size_type aElementSize,
const void* aElements,
array_size_type aNumElements)
{
*array = (Array) malloc(sizeof(struct _Array));
if (!*array)
return false;
(*array)->contents = malloc(aElementSize * (kArrayIncrementSize >=
aNumElements ? kArrayIncrementSize : aNumElements));
if (!(*array)->contents) {
free(*array);
return false;
}
if (aNumElements > 0)
memcpy((*array)->contents, aElements, aElementSize *
aNumElements);
(*array)->capacity = kArrayIncrementSize >= aNumElements ?
kArrayIncrementSize : aNumElements;
(*array)->size = aNumElements;
(*array)->elementSize = aElementSize;
return true;
}
void ArrayDestroy(Array array,
ArrayElementsDestructorCallback aDestructorCallback)
{
ArrayClear(array, aDestructorCallback);
free(ArrayElements(array));
free(array);
}
//
// Helpers
//
// Increases the size of the array if necessary
// @param aCapacity the required minimum capacity
// @return true if the array was already large enough or
if the
// reallocation succeeded; false on OOM error
static bool ArrayEnsureCapacity(Array array,
array_size_type aCapacity)
{
void* newContents;
array_size_type incrementSize;
if (array->capacity >= aCapacity)
return true;
incrementSize = aCapacity - array->capacity;
if (incrementSize < kArrayIncrementSize)
incrementSize = kArrayIncrementSize;
newContents = realloc(array->contents, array->elementSize * (array-
capacity + incrementSize));
if (!newContents)
return false;
array->contents = newContents;
array->capacity += incrementSize;
return true;
}
// Destroys a range of elements by calling a destructor callback on
them
static inline void ArrayDestructRange(Array array,
array_index_type aStart,
array_size_type aCount,
ArrayElementsDestructorCallback
aDestructorCallback)
{
if (aDestructorCallback) {
array_index_type i;
for (i = aStart; i < aCount; ++i)
aDestructorCallback((char*) ArrayElements(array) + i *
ArrayElementSize(array));
}
}
// Shifts a sequence of elements a number of positions forward or
backward
static inline void ArrayShiftRange(Array array,
array_index_type aOldStart,
array_index_type aNewStart,
array_size_type aCount)
{
if (aOldStart != aNewStart && aCount > 0)
memmove((char*) ArrayElements(array) + aNewStart *
ArrayElementSize(array),
(char*) ArrayElements(array) + aOldStart *
ArrayElementSize(array),
aCount * ArrayElementSize(array));
}
static inline void ArrayAssignRange(Array array,
array_index_type aStart,
array_size_type aCount,
const void* aItems)
{
memcpy((char*) ArrayElements(array) + aStart * ArrayElementSize
(array),
aItems, aCount * ArrayElementSize(array));
}
//
// Array functions
//
void* ArrayElements(Array array)
{
return array->contents;
}
const void* ArrayElements_const(const Array array)
{
return array->contents;
}
array_size_type ArraySize(const Array array)
{
return array->size;
}
array_size_type ArrayElementSize(const Array array)
{
return array->elementSize;
}
array_index_type ArrayIndexOf(const Array array,
const void* aItem,
array_index_type aStart,
ArrayElementsComparatorCallback
aComparatorCallback)
{
array_index_type i;
if (aStart < 0 || aStart >= ArraySize(array))
return -1;
for (i = aStart; i < ArraySize(array); ++i)
if (aComparatorCallback((char*) ArrayElements_const(array) + i
* ArrayElementSize(array), aItem) == 0)
return i;
return -1;
}
void* ArrayReplaceElementsAt(Array array,
array_index_type aStart,
array_size_type aCount,
const void* aItems,
array_size_type aNumItems,
ArrayElementsDestructorCallback
aDestructorCallback)
{
if (aStart < 0 || aStart >= ArraySize(array) || aStart + aCount >
ArraySize(array))
return 0;
if (!ArrayEnsureCapacity(array, ArraySize(array) - aCount +
aNumItems))
return 0;
ArrayDestructRange(array, aStart, aCount, aDestructorCallback);
ArrayShiftRange(array, aStart + aCount, aStart + aNumItems,
ArraySize(array) - (aStart + aCount));
ArrayAssignRange(array, aStart, aNumItems, aItems);
array->size = ArraySize(array) - aCount + aNumItems;
return ArrayAt(array, aStart);
}
void* ArrayAppendElements(Array array,
const void* aItems,
array_size_type aNumItems)
{
if (!ArrayEnsureCapacity(array, ArraySize(array) + aNumItems))
return 0;
ArrayAssignRange(array, ArraySize(array), aNumItems, aItems);
array->size += aNumItems;
return ArrayAt(array, ArraySize(array));
}
void ArrayRemoveElementsAt(Array array,
array_index_type aStart,
array_size_type aCount,
ArrayElementsDestructorCallback
aDestructorCallback)
{
if (aStart < 0 || aStart >= ArraySize(array) || aStart + aCount >
ArraySize(array))
return;
ArrayDestructRange(array, aStart, aCount, aDestructorCallback);
ArrayShiftRange(array, aStart + aCount, aStart, ArraySize(array) -
(aStart + aCount));
array->size -= aCount;
}
bool ArrayRemoveElement(Array array,
const void* aItem,
ArrayElementsComparatorCallback
aComparatorCallback,
ArrayElementsDestructorCallback
aDestructorCallback)
{
array_index_type i = ArrayIndexOf(array, aItem, 0,
aComparatorCallback);
if (i == -1)
return false;
ArrayRemoveElementAt(array, i, aDestructorCallback);
return true;
}
void* ArrayAt(Array array,
array_index_type aIndex)
{
return (char*) ArrayElements(array) + aIndex * array->elementSize;
}
const void* ArrayAt_const(const Array array,
array_index_type aIndex)
{
return (char*) ArrayElements_const(array) + aIndex * array-
}