Produire des composants logiciels réutilisables est un objectif de l'ingénierie du logiciel. En particulier, on souhaite que les structures de données (que ce soit des tables, des piles, des arbres, etc) puissent être utilisées quel que soit le type des objets qui y sont rangés. C'est la notion de structures de données et de fonctions génériques.
C ne dispose pas d'une notion générale de généricité, à la différence de
CAML, d'Ada ou de C++. Cependant, le type void* des pointeurs
à void permet d'implémenter facilement des structures de
données quand ces données sont des pointeurs vers des objets plutôt que
les objets eux-mêmes. Le principe est que l'on peut affecter un
void* à une variable de type *
, pour un type
quelconque et inversement, de façon sûre, et sans qu'il soit nécessaire
d'effectuer une coercition.
Reprenons l'exemple du parcours en profondeur d'un arbre à l'aide d'une pile. Plutôt que d'utiliser des piles dont le type des données est << pointeur de cellule >>, c'est-à-dire d'écrire dans le fichier définissant les opérations sur les piles
typedef tree stack_data;
ce qui devrait être modifié si on changeait le type des cellules, il est préférable de définir
typedef void *stack_data;
La fonction de parcours depth_first_iter
n'a pas besoin d'être
modifiée.
Signalons que C++ dispose d'un mécanisme beaucoup plus général pour la définition de types et de fonctions génériques, les templates.
Pour obtenir une organisation modulaire, on rassemblera les déclarations des fonctions publiques (exportées) d'une structure de données dans un fichier d'en-tête suffixé en .h. Les déclarations des fonctions importées (c'est-à-dire d'autres fonctions que les fonctions publiques utilisent) figureront dans un autre fichier d'en-tête. Cela permet, après compilation, de fournir d'une part le .h comme interface d'exportation, et d'autre part le .o, comme module objet ; le .C, source de l'implémentation, n'a pas besoin d'être fourni.
Reprenons par exemple la structure de données << pile de void* >>, qui peut servir à implémenter des parcours en profondeur dans des arbres, et plus généralement dans des graphes. Son interface sera un fichier
// stack.h const int MAX=100; typedef void *stack_data; struct stack { int height; stack_data content[MAX]; }; void mk_empty_stack(stack& s); bool is_empty_stack(const stack& p); void push(const stack_data& x, stack& p); stack_data top(const stack& p); stack_data pop(stack& p);
Un programme tree.C qui utilise une pile de pointeurs devra inclure dans son source cet en-tête stack.h et être lié à la compilation avec le module objet stack.o :
unix% g++ -c tree.C unix% g++ -o tree tree.o stack.o
ou, en une seule commande :
unix% g++ tree.C stack.o -o tree