This code snippet is a research attempt to find means for dealing with dynamic polymorphism (in C): two or more related "classes" share a common interface. Here is my best shot:
#include <stdio.h>
#include <stdlib.h>
#define CALL(INTERFACE_PTR, METHOD_NAME, ...) \
INTERFACE_PTR->METHOD_NAME(INTERFACE_PTR->object, ##__VA_ARGS__)
#define DESTRUCT(INTERFACE_PTR) INTERFACE_PTR->destruct(INTERFACE_PTR->object)
/*******************************************************************************
* Dummy data structures used for the demonstration. *
*******************************************************************************/
typedef struct int_triple {
int a;
int b;
int c;
} int_triple;
typedef struct int_pointer_triple {
int* a;
int* b;
int* c;
} int_pointer_triple;
void int_triple_init(int_triple* it)
{
it->a = 0;
it->b = 0;
it->c = 0;
}
void int_pointer_triple_init(int_pointer_triple* ipt)
{
ipt->a = malloc(sizeof(int));
ipt->b = malloc(sizeof(int));
ipt->c = malloc(sizeof(int));
*ipt->a = 0;
*ipt->b = 0;
*ipt->c = 0;
}
void int_triple_set(void* int_triple_ptr, int a, int b, int c)
{
int_triple* it = (int_triple*) int_triple_ptr;
it->a = a;
it->b = b;
it->c = c;
printf("int_triple_set(%d, %d, %d)\n", a, b, c);
}
void int_pointer_triple_set(void* int_triple_pointer_ptr, int a, int b, int c)
{
int_pointer_triple* ipt = (int_pointer_triple*) int_triple_pointer_ptr;
*ipt->a = a;
*ipt->b = b;
*ipt->c = c;
printf("int_pointer_triple_set(%d, %d, %d)\n", a, b, c);
}
int int_triple_get_sum(void* int_triple_ptr)
{
int_triple* it = (int_triple*) int_triple_ptr;
puts("int_triple_get_sum()");
return it->a + it->b + it->c;
}
int int_pointer_triple_get_sum(void* int_pointer_triple_ptr)
{
int_pointer_triple* ipt = (int_pointer_triple*) int_pointer_triple_ptr;
puts("int_pointer_triple_get_sum()");
return *ipt->a + *ipt->b + *ipt->c;
}
int int_triple_get_product(void* int_triple_ptr)
{
int_triple* it = (int_triple*) int_triple_ptr;
puts("int_triple_get_product()");
return it->a * it->b * it->c;
}
int int_pointer_triple_get_product(void* int_pointer_triple_ptr)
{
int_pointer_triple* ipt = (int_pointer_triple*) int_pointer_triple_ptr;
puts("int_pointer_triple_get_product()");
return *ipt->a * *ipt->b * *ipt->c;
}
void int_triple_destruct(void* int_triple_ptr)
{
free(int_triple_ptr);
puts("int_triple_destruct()");
}
void int_pointer_triple_destruct(void* int_pointer_triple_ptr)
{
int_pointer_triple* ipt = (int_pointer_triple*) int_pointer_triple_ptr;
free(ipt->a);
free(ipt->b);
free(ipt->c);
free(ipt);
puts("int_pointer_triple_destruct");
}
/*******************************************************************************
* Interface stuff. *
*******************************************************************************/
typedef struct int_triple_interface {
void* object;
void (*set) (void*, int, int, int);
int (*get_sum) (void*);
int (*get_product) (void*);
void (*destruct) (void*);
} int_triple_interface;
int_triple_interface* new_int_triple()
{
int_triple_interface* interface = malloc(sizeof(*interface));
int_triple* it = malloc(sizeof(*it));
int_triple_init(it);
interface->object = it;
interface->set = int_triple_set;
interface->get_sum = int_triple_get_sum;
interface->get_product = int_triple_get_product;
interface->destruct = int_triple_destruct;
return interface;
}
int_triple_interface* new_int_pointer_triple()
{
int_triple_interface* interface = malloc(sizeof(*interface));
int_pointer_triple* ipt = malloc(sizeof(*ipt));
int_pointer_triple_init(ipt);
interface->object = ipt;
interface->set = int_pointer_triple_set;
interface->get_sum = int_pointer_triple_get_sum;
interface->get_product = int_pointer_triple_get_product;
interface->destruct = int_pointer_triple_destruct;
return interface;
}
int main() {
int_triple_interface* interface;
int sum;
int product;
puts("--- int_triple ---");
interface = new_int_triple();
CALL(interface, set, 2, 3, 4);
sum = CALL(interface, get_sum);
product = CALL(interface, get_product);
DESTRUCT(interface);
printf("sum = %d, product = %d.\n", sum, product);
puts("\n--- int_pointer_triple ---");
interface = new_int_pointer_triple();
CALL(interface, set, 5, 6, 7);
sum = CALL(interface, get_sum);
product = CALL(interface, get_product);
DESTRUCT(interface);
printf("sum = %d, product = %d.\n", sum, product);
}
The output from the main
is as follows:
--- int_triple --- int_triple_set(2, 3, 4) int_triple_get_sum() int_triple_get_product() int_triple_destruct() sum = 9, product = 24. --- int_pointer_triple --- int_pointer_triple_set(5, 6, 7) int_pointer_triple_get_sum() int_pointer_triple_get_product() int_pointer_triple_destruct sum = 18, product = 210.
Critique request
I would like to hear comments on:
- naming conventions,
- coding conventions,
- overall comments,
- design pattern,
- anything else.
1 Answer 1
Your approach has at least two unfortunate aspects:
- Your approach to
interface->object
uses an extra heap-allocation. - Adding a new "method" to
int_triple_interface
requires increasing the size of every "int triple" "object" in the program bysizeof(void(*)())
. - Nit: Your
DESTRUCT(x)
macro doesn't actuallyfree(x)
.
The first problem could be solved by allocating the "object data" directly after the "interface data", with allowances for alignment/padding:
void int_triple_destruct(void* int_triple_ptr)
{
puts("int_triple_destruct() is now a no-op");
}
int_triple_interface* new_int_triple()
{
int_triple_interface* interface = malloc(sizeof(*interface) + sizeof(int_triple));
int_triple* it = (void*)(interface + 1);
int_triple_init(it);
interface->object = it;
interface->set = int_triple_set;
interface->get_sum = int_triple_get_sum;
interface->get_product = int_triple_get_product;
interface->destruct = int_triple_destruct;
return interface;
}
The second problem could be solved by storing all your function pointers away in a static data table: one table per object type, instead of one table per object instance.
#define CALL(INTERFACE_PTR, METHOD_NAME, ...) \
INTERFACE_PTR->vtable->METHOD_NAME(INTERFACE_PTR->object, ##__VA_ARGS__)
#define DESTRUCT(INTERFACE_PTR) INTERFACE_PTR->vtable->destruct(INTERFACE_PTR->object)
typedef struct {
void (*set) (void*, int, int, int);
int (*get_sum) (void*);
int (*get_product) (void*);
void (*destruct) (void*);
} int_triple_vtable_t;
int_triple_vtable_t int_triple_vtable = {
int_triple_set,
int_triple_get_sum,
int_triple_get_product,
int_triple_destruct,
};
typedef struct {
void* object;
int_triple_vtable_t* vtable;
} int_triple_interface;
int_triple_interface* new_int_triple()
{
int_triple_interface* interface = malloc(sizeof(*interface) + sizeof(int_triple));
int_triple* it = (void*)(interface + 1);
int_triple_init(it);
interface->object = it;
interface->vtable = int_triple_vtable;
return interface;
}
You probably don't really need the object
pointer at all (since now it just points to (char *)interface + k
for some known offset k
), but I haven't thought about it too much.
In general, the question "how do I do OOP in C" can be answered by looking at what C++ does and then slavishly copying it. That's where I got the idea of "vtables" here, for example. Your object
pointer is roughly equivalent to a C++ "virtual base class". And if you really want to do efficient, type-safe OOP in C, you're eventually going to have to reckon with the concepts of "copy construction" and "move construction", both of which you can find implemented in C++.