2
\$\begingroup\$

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:

  1. naming conventions,
  2. coding conventions,
  3. overall comments,
  4. design pattern,
  5. anything else.
\$\endgroup\$
6
\$\begingroup\$

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 by sizeof(void(*)()).
  • Nit: Your DESTRUCT(x) macro doesn't actually free(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++.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.