I have a struct Queue
that stores at most 15 elements of type integer.
typedef struct Queue Queue;
struct Queue
{
int buffer[15]; //only store 15 integer
int head;
int space;
int tail;
int (*isFull)(Queue_int* const me);
int (*isEmpty)(Queue_int* const me);
int (*getSize)(Queue_int* const me);
void (*insert)(Queue_int* const me, int k); //insert integer only
int (*remove)(Queue_int* const me);
};
I want to write a general queue library that can store arbitrary number of elements of any type.
I wrote a macro to create a general interface for other interfaces.
/*queue.h*/
#define Queue_Define(type, size)\
typedef struct Queue_##type Queue_##type;\
struct Queue_##type \
{ \
type buffer[size]; \
int head; \
int space; \
int tail; \
int (*isFull)(Queue_##type* const me); \
int (*isEmpty)(Queue_##type* const me); \
int (*getSize)(Queue_##type* const me); \
void (*insert)(Queue_##type* const me, type k); \
int (*remove)(Queue_##type* const me); \
}\
For example:
/*queue_employee.h*/
#include "queue.h"
/*expanded from Queue_Define(employee, 15);*/
typedef struct Queue_employee Queue_employee;
struct Queue_employee
{
employee buffer[15];
int head;
int space;
int tail;
int (*isFull)(Queue_employee* const me);
int (*isEmpty)(Queue_employee* const me);
int (*getSize)(Queue_employee* const me);
void (*insert)(Queue_employee* const me, employee k);
int (*remove)(Queue_employee* const me);
};
void Queue_employee_Init
(
Queue_employee* const me, //oops! how do client know struct name is Queue_employee?
int (*isFullfunction)(Queue_employee* const me), //ditto
int (*isEmptyfunction)(Queue_employee* const me), //ditto
int (*getSizefunction)(Queue_employee* const me), //ditto
void (*insertfunction)(Queue_employee* const me, employee k), //ditto
int (*removefunction)(Queue_employee* const me) //ditto
);
But with this way the client have to learn how to use this macro.
Any suggestions?
There are more or less fancy ways that you can make generic interfaces with macros, but I'd recommend to start with the "old school" way of generic programming in C, which means using void*
in combination with size variables.
First some notes about your current code:
I'd drop the "member function" syntax from the struct, since it's simply not meaningful in C. foo.isFull(foo)
is not the slightest more user-friendly than queue_isFull(foo)
. The function pointers add nothing but needless complexity. If C supported a this
pointer like C++, it would be another story. But it does not, so there's nothing gained from trying to emulate C++ style syntax.
const
qualifying the pointer arguments with Queue_int* const queue
is not a meaningful API. The caller doesn't care what your function does internally with your local variable queue
. Have it point wherever you like - the caller doesn't care. I believe you are actually mistaking this syntax for const correctness, which is good and correct practice. You should then have written int isFull(const Queue_int* queue)
which means that the function isFull
won't modify the pointed-at queue, which makes perfect sense.
You should consider using private encapsulation. The caller should not be allowed to access members like buffer
directly. This becomes even more important if we start using void*
, because they are type unsafe. We can achieve private encapsulation with the concept of opaque types.
Opaque type goes like this:
// queue.h
typedef struct queue_t queue_t;
This is a forward declaration of a structure. The user who includes queue.h won't be able to access members inside the struct, nor to declare an instance of the struct. They will only be able to work with pointers to the struct. (It works exactly like an abstract base class in C++ if that concept is familiar.) The actual struct declaration will only be visible to the private .c file.
Next up you'd drop a number of available functions in the header, including a "constructor" and "destructor". For example:
queue_t* queue_init (size_t item_size, size_t def_queue_size);
void queue_delete (queue_t* queue);
bool queue_set_item (queue_t* queue, size_t index, const void* item);
bool queue_get_item (const queue_t* queue, size_t index, void* item);
Let the caller provide pointers and sizes so that it becomes type-generic. (I'm just making some nonsense member functions here, ignoring that this is a queue data type.)
The full header would look like this:
// queue.h
#ifndef QUEUE_H
#define QUEUE_H
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
typedef struct queue_t queue_t;
queue_t* queue_init (size_t item_size, size_t def_queue_size);
void queue_delete (queue_t* queue);
bool queue_set_item (queue_t* queue, size_t index, const void* item);
bool queue_get_item (const queue_t* queue, size_t index, void* item);
#endif /* QUEUE_H */
And then a matching C file with the implementation of the struct as well as the functions:
// queue.c
#include "queue.h"
struct queue_t
{
size_t item_size;
size_t queue_size;
uint8_t data[];
};
queue_t* queue_init (size_t item_size, size_t def_queue_size)
{
queue_t* result = malloc (sizeof *result + item_size*def_queue_size);
if(result == NULL)
return NULL;
result->item_size = item_size;
result->queue_size = def_queue_size;
memset(result->data, 0, item_size*def_queue_size);
return result;
}
void queue_delete (queue_t* queue)
{
free(queue);
}
bool queue_set_item (queue_t* queue, size_t index, const void* item)
{
if(index >= queue->queue_size)
return false;
memcpy(&queue->data[index * queue->item_size], item, queue->item_size);
return true;
}
Example of caller code:
#include <stdio.h>
#include "queue.h"
int main(void)
{
queue_t* int_queue = queue_init(sizeof(int), 10);
int x = 666;
int y;
(void) queue_set_item(int_queue, 5, &x);
(void) queue_get_item(int_queue, 5, &y);
printf("%d\n", y);
queue_delete(int_queue);
queue_t* string_queue = queue_init(sizeof(char[10]), 10);
char str1[10] = "Hello";
char str2[10];
(void) queue_set_item(string_queue, 3, str1);
(void) queue_get_item(string_queue, 3, str2);
puts(str2);
queue_delete(string_queue);
return 0;
}
This of course just a silly artificial example and nothing like a queue ADT, but hopefully you get the idea of how to implement one, type-generic.