Let's assume there is an employee ADT, such as
//employee.h
typedef struct employee_t employee_t;
employee_t* employee_create(char* company, char* department, char* position);
void employee_free(employee_t* me);
, and client code would be
#include "employee.h"
employee_t* Kevin = employee_create("Facebook", "Marketing", "Sales");
employee_t* John = employee_create("Microsoft", "R&D", "Engineer");
Now client wanted to use list ADT to insert Kevin and John to list for some task.
//list.h
typedef struct list_t list_t;
list_t* list_create(/*might have some arguments*/);
So client code would then be
#include "employee.h"
#include "list.h"
employee_t* Kevin = employee_create("Facebook", "Marketing", "Sales");
employee_t* John = employee_create("Microsoft", "R&D", "Engineer");
list_t* employee = list_create(/*might have some arguments*/);
list_insert(employee, Kevin);
list_insert(employee, John);
employee_free(Kevin);
employee_free(John);
list_print(employee); //Oops! How to print structure that you can't see?
Because employee is encapsulated by opaque pointer, there is no way for list to copy it.
How to write ADT and implementation for list?
CodePudding user response:
The usual way to do this is to have your list structure store the data as a void*
. For example, assmuming your list is a singly linked list:
struct list_t
{
void *data;
struct list_t *next;
};
Now list_insert
whould be something like this:
list_t *list_insert(list_t *head, void *data)
{
list_t *newHead = (list_t*)malloc(sizeof(list_t));
newHead->data;
newHead->next = head;
return newHead;
}
If you want to hide away the implementation of the struct then you can add methods to extract the data. For example:
void *list_get_data(list_t *head)
{
return head->data;
}
CodePudding user response:
How do you write generic list without knowing the implementation of structure?
Create functions that handle the structure abstractly.
How to write ADT and implementation for list?
list_create();
needs to pass in helper function pointers for the particular object type to perform various tasks abstractly.
A copy function like
void *employee_copy(const void *emp)
solist_insert(employee, Kevin);
knows how to copyKevin
.A free function like
void employee_free(void *emp)
solist_uninsert(employee_t)
can free the list when destroyed or members removed one-by-one.A print function
int employee_print(void *emp)
solist_print(employee_t)
knows how to print each member of its list.Possibly others.
Rather than pass in 3 function pointers, consider passing in a struct
that contains these pointers, then the list only needs the overhead of 1 pointer: list_create(employee_t_function_list)
You are taking your first steps toward re-writing C
CodePudding user response:
You can use something called intrusive list. This concept is heavily used in Linux kernel. All you need is to embed the node into the struct and let the generic code operate only on this struct member.
#include <stddef.h>
struct list_node {
struct list_node *next;
};
struct list_head {
struct list_node *first;
};
// translates pointer to a node to pointer to containing structure
#define list_entry(ptr, type, member) \
(type*)((char*)ptr - offsetof(type, member))
void list_insert(struct list_head *head, struct list_node *node) {
node->next = head->first;
head->first = node;
}
#define LIST_FOREACH(it, head) \
for (struct list_node *it = (head)->first; it; it = it->next)
The interface can be easily extended by other helpers like list_is_empty
, list_first
, list_remove_first
, embed size to struct list_head
, or even add a macro LIST_FOREACH
to wrap a for loop over the list elements.
Exemplary usage:
typedef struct {
char *name;
struct list_node node;
} employee_t;
typedef struct {
char *name;
struct list_head employees;
} employer_t;
employer_t company = { .name = "The Company" };
employee_t bob = { .name = "Bob" };
employee_t mark = { .name = "Mark" };
list_insert(&company.employees, &bob.node);
list_insert(&company.employees, &mark.node);
printf("Employees of %s:\n", company.name);
LIST_FOREACH(n, &company.employees) {
employee_t *e = list_entry(n, employee_t, node);
printf("%s\n", e->name);
}
Prints:
Employees of The Company:
Mark
Bob
Note that the list_*
interface can easily used for other types as well.
See article for more information about using this concept for double-linked list.
Edit
Note that list_entry
invokes a subtle Undefined Behavior.
It is related to performing pointer arithmetics outside of the struct member object but still within a parent object.
Note that any objects can be treated as an array of char
s.
This code will work on all major compilers and it very unlikely to ever fail because it would break a lot of existing and heavily used code (like Linux kernel or Git).
The issue could be circumvented by forming a pointer to struct list_node
not as &bob.node
but rather using a pointer arithmetics from on a pointer to bob
. The result would be:
(struct list_node*)((char*)&bob offsetof(employee_t, node))
However, this syntax is really nasty, so personally I would go for &bob.node
.