Generische Programmierung in C



  • Hallo!
    Ich habe mir in C etwas gebastelt, mit dem ich quasi "Templates" benutzen kann. Damit ist es möglich über ein Makro Listen etc verschiedenen Types zu verwenden, da ich persönlich nicht auf void-Pointer stehe.
    Das Einzige Problem, das ich habe ist, dass diese Templates header-only sind, was zur Folge hat, dass ich im Endeffekt bei Verwendung der gleichen Liste (mit selbem Namen etc) in unterschiedlichen *.c-Files Linker-Fehler erhalte. Allerdings kann ich es eigentlich nicht so machen, dass ich diese Listen lediglich private verwende. Hat irgendwer von euch da vielleicht einen Hinweis, wie ich die Linker-Fehler umgehen kann?
    Hier ist einmal die Implementierung eines Arrays:

    #ifndef ARRAY_H_
    #define ARRAY_H_
    
    #include <stdlib.h>
    #include <math.h>
    
    typedef enum MODES { AUTOCAP, FIXEDCAP } MODES;
    #define INITCAP	10
    #define ARRAY_PROTOTYPE(name, type)\
    \
    /**\
     * basic structure of an array\
     */\
    typedef struct name##_array\
    {\
    	/**\
    	 * pointer to the array of elements\
    	 */\
    	type * elements;\
    	/**\
    	 * the maximum capacity of the array\
    	 */\
    	size_t capacity;\
    	/**\
    	 * sets the mode to resize strategy, default is autocap\
    	 */\
    	 MODES mode;\
    } name##_array;\
    \
    /**\
     * typedef for the array callback called, e.g. in foreach method with the current content element\
     */\
    typedef void (* name##_array_callback) (type);\
    \
    /**\
     * initializes the array specifying the capacity and instantiating the elements array\
     * @param ar the array to be initialized\
     */\
    int name##_array_init_auto (name##_array * ar);\
    /**\
     * initializes the array specifying the capacity and instantiating the elements array\
     * @param ar the array to be initialized\
     */\
    int name##_array_init_fixed (name##_array * ar, size_t cap);\
    /**\
     * adds an element element to the array, resizing the structure, if necessary\
     * @param ar the array to which an element should be added at the next free place\
     * @param elem the element to be set\
     */\
    int name##_array_set (name##_array * ar, type elem, size_t pos);\
    /**\
     * ensures the capacity of the array by trying to retrieve the element at the specified position\
     * if the position is out of bounds, it will resize the array\
     * @param ar the array whose capacity is to be ensured\
     * @param pos the position to access in write\
     */\
    int name##_array_ensure_capacity (name##_array * ar, size_t pos);\
    /**\
     * copies all contents of the first array to the second\
     * @param src the source array\
     * @param dest the destination to copy to\
     */\
    int name##_array_copy (name##_array * src, name##_array * dest);\
    /**\
     * returns a reference to the given element in the array, if index out of bounds --> NULL\
     * @param ar the array where the element shall be retrieved from\
     * @param pos the index of the element\
     */\
    type * name##_array_get (name##_array * ar, size_t pos);\
    /**\
     * destroys the given array deleting the elements array and resetting all values\
     * @param ar the array to be deleted\
     */\
    void name##_array_destroy (name##_array * ar);\
    /**\
     * iterates over all elements in the given array letting the caller decide what to do with the content\
     * calling the callback function each time when a new element is encountered\
     * @param ar the array over which the operator has to iterate\
     */\
    void name##_array_foreach (name##_array * ar, name##_array_callback callback);\
    \
    /**\
     * initializes the array specifying the capacity and instantiating the elements array\
     * @param ar the array to be initialized\
     */\
    int name##_array_init_auto (name##_array * ar)\
    {\
    	/* allocate */\
    	ar->elements = malloc(INITCAP * sizeof(type));\
    	/* no memory available */\
    	if(ar->elements == NULL)\
    	{\
    		return -1;\
    	}\
    	else\
    	{\
    		ar->capacity = INITCAP;\
    	}\
    	\
    	ar->mode = AUTOCAP;\
    \
    	/* return the success */\
    	return 0;\
    }\
    \
    /**\
     * initializes the array specifying the capacity and instantiating the elements array\
     * @param ar the array to be initialized\
     */\
    int name##_array_init_fixed (name##_array * ar, size_t cap)\
    {\
    	ar->elements = malloc((cap) * sizeof(type));\
    	/* no memory available */\
    	if(ar->elements == NULL)\
    	{\
    		return -1;\
    	}\
    	else\
    	{\
    		ar->capacity = cap;\
    	}\
    \
    	ar->mode = FIXEDCAP;\
    \
    	/* return the success */\
    	return 0;\
    }\
    \
    /**\
     * adds an element element to the array, resizing the structure, if necessary\
     * @param ar the array to which an element should be added at the next free place\
     * @param elem the element to be set\
     */\
    int name##_array_set (name##_array * ar, type elem, size_t pos)\
    {\
    	/* ensure that enough capacity is present */\
    	if(name##_array_ensure_capacity(ar, pos) < 0)\
    	{\
    		return -1;\
    	}\
    \
    	/* set the content element */\
    	ar->elements[pos] = elem;\
    \
    	return 0;\
    }\
    \
    /**\
     * ensures the capacity of the array by trying to retrieve the element at the specified position\
     * if the position is out of bounds, it will resize the array\
     * @param ar the array whose capacity is to be ensured\
     * @param pos the position to access in write\
     */\
    int name##_array_ensure_capacity (name##_array * ar, size_t pos)\
    {\
    	size_t capacity = 0;\
    	if(ar->capacity > pos)\
    	{\
    		return 0;\
    	}\
    	/* grant at least enough capacity to access the element */\
    	if(ar->mode == AUTOCAP)\
    	{\
    		capacity = (int) ceil((double) (pos + 1) / (double) INITCAP) * INITCAP;\
    	}\
    	else\
    	{\
    		capacity = pos;\
    	}\
    \
    	ar->elements = realloc(ar->elements, sizeof(type) * capacity);\
    	if(ar->elements == NULL)\
    	{\
    		return -1;\
    	}\
    	ar->capacity = capacity;\
    \
    	return 0;\
    }\
    \
    /**\
     * copies all contents of the first array to the second\
     * @param src the source array\
     * @param dest the destination to copy to\
     */\
    int name##_array_copy (name##_array * src, name##_array * dest)\
    {\
    	int error = 0, i = 0;\
    	error = name##_array_init_fixed(dest, src->capacity);\
    	if(error < 0)\
    	{\
    		return error;\
    	}\
    	/* ensure that both arrays are of same size */\
    	/*error = name##_array_ensure_capacity(dest, src->capacity - 1);\
    	if(error < 0)\
    	{\
    		return error;\
    	}*/\
    \
    	/* perform actual copy operation */\
    	for(i = 0; i < src->capacity; ++i)\
    	{\
    		name##_array_set(dest, *name##_array_get(src, i), i);\
    	}\
    	dest->mode = src->mode;\
    \
    	return error;\
    }\
    \
    /**\
     * returns a reference to the given element in the array, if index out of bounds --> NULL\
     * @param ar the array where the element shall be retrieved from\
     * @param pos the index of the element\
     */\
    type * name##_array_get (name##_array * ar, size_t pos)\
    {\
    	if(pos >= ar->capacity)\
    	{\
    		return NULL;\
    	}\
    \
    	return &ar->elements[pos];\
    }\
    \
    /**\
     * destroys the given array deleting the elements array and resetting all values\
     * @param ar the array to be deleted\
     */\
    void name##_array_destroy (name##_array * ar)\
    {\
    	ar->capacity = 0;\
    \
    	free(ar->elements);\
    }\
    \
    


  • Zunächst mal das ganze Problem entschärfen.

    typedef struct name##_array
    

    wird zu

    typedef struct name##type##_array

    Und vielleicht Privatheit zulassen mit einem bool-Parameter, der static vor alle Funktionen schreibt.



  • Jo, danke so weit, das vermeidet zumindest Namensprobleme zu einem gewissen Grad.
    Allerdings löst das Ganze auch nicht das prinzipielle Linker-Problem, das sich durch Definitionen in den Headern ergibt. Wenn ich jetzt die Zugriffe wirklich nur noch in der *.c-File gewähre, hab ich keinerlei Chance mehr auf Interna zuzugreifen und muss die Listenzugriffe wieder von außen weiterleiten, was sehr schade wäre (bzw mehr Aufwand, als die Implementierungen mit void-Pointern zu basteln).
    So was, wie ne Makro-Definition, die ich abrufen könnte in einer .c-Datei, um zu überprüfen, ob was gesetzt ist, dass ich quasi die Implementierung aus der Header auslagern könnte und entsprechend dann damit den Linker steuern, hab ich leider noch nicht gefunden.
    Hoffe, dass mir irgendwer helfen kann, fand die Idee ganz nett eigentlich, allerdings mit den Linker-Errors nicht wirklich zu verwenden, oder nur auf Umwegen, dann nehm ich lieber void
    ...
    Hab leider auch schon verschiedene andere Container mit der Idee implementiert, die ich alle jetzt umbauen muss/müsste.

    Viele Grüße und schönen Dank so weit, hoffe, dass trotzdem noch jemand helfen kann.



  • Du musst die Funktionen inline n.



  • ach so, wenn ich alle Funktionen, die ich in der Header verwende als inline deklariere, bekomme ich, selbst bei theoretischen Namenskonflikten keine Linkerfehler?
    Geht das Ganze dann irgendwie auf Kosten der Effizienz?

    Danke so weit!


Anmelden zum Antworten