0

Let's say I have Stack that I want to use to store various different types. If I define the interface as follows:

// stack.h
typedef struct Stack {
    size
    push // function pointers
    pop
    etc.
};

And let's say I want to support two different stack types, and so I create:

// stack.c
Stack person_stack;
person_stack->push = push_to_person_stack;

Stack animal_stack;
animal_stack->push = push_to_animal_stack;

How can I make it such that the push_to_<type>_stack is effectively private and the caller of that stack is only able to see the Stack->push function? Or maybe that's not possible in C and you need an OO language to do it, but what would be an example of having a unified interface for this?

David542
  • 104,438
  • 178
  • 489
  • 842
  • Uncle Bob has asserted that OOP can be implemented in C, but I don't recall exactly what video I watched. If you're willing to do the research, he might be a place to start. http://cleancoder.com/products – nicomp Jan 29 '21 at 21:07
  • @nicomp -- I see, so what's the more common way to do the above? Just duplicate the code and have the long-named methods for each? – David542 Jan 29 '21 at 21:11
  • Off the top of my head I don't know since you can't do run-time binding in C. There's a little here about working around that issue: https://stackoverflow.com/questions/16420628/what-are-static-and-dynamic-binding-in-c-strictly-c-not-c – nicomp Jan 29 '21 at 21:15

1 Answers1

1

You can use function pointers to emulate methods in other OOP languages but still you need to pass the instance to the method otherwise there is no way to know on which stack to push. Also using void* would make more problems than it solves.

struct Stack {
    void (*push)(Stack* self, void* data); //< self here
}

Here is a way that I use to emulate template/generics in c by using macros (reference : https://github.com/wren-lang/wren/blob/main/src/vm/wren_utils.h#L16)


#define DECLARE_STACK(type)                                   \
    typedef struct {                                          \
        type* data;                                           \
        int size;                                             \
        int capacity;                                         \
    } type##Stack;                                            \
    void type##Stack_push(type##Stack* self, type value);     \
    type type##Stack_pop(type##Stack* self);                  \
    void type##Stack_init(type##Stack* self);                 \

#define DEFINE_STACK(type)                                                        \
    void type##Stack_push(type##Stack* self, type value) {                        \
        if (self->capacity <= self->size + 1) {                                   \
            self->capacity = self->capacity * 2;                                  \
            self->data = realloc(self->data, sizeof(type) * self->capacity);      \
        }                                                                         \
        self->data[self->size] = value;                                           \
        self->size++;                                                             \
    }                                                                             \
                                                                                  \
    type type##Stack_pop(type##Stack* self) {                                     \
        self->size--;                                                             \
        return self->data[self->size];                                            \
    }                                                                             \
                                                                                  \
    void type##Stack_init(type##Stack* self) {                                    \
        self->size = 0;                                                           \
        self->capacity = 2;                                                       \
        self->data = malloc(sizeof(type) * self->capacity);                       \
    }                                                                             \
    

typedef struct Person {
    int id;
} Person;

DECLARE_STACK(Person); // in person.h
DEFINE_STACK(Person);  // in person.c


int main() {

    Person p1, p2, p3, p4;
    p1.id = 1; p2.id = 2; p3.id = 3; p4.id = 4;

    PersonStack stack;
    
    PersonStack_init(&stack);
    PersonStack_push(&stack, p1);
    PersonStack_push(&stack, p2);
    PersonStack_push(&stack, p3);

    Person p;
    p = PersonStack_pop(&stack); // p.id = 3
    p = PersonStack_pop(&stack); // p.id = 2

    PersonStack_push(&stack, p4);
    p = PersonStack_pop(&stack); // p.id = 4
    p = PersonStack_pop(&stack); // p.id = 1


    return 0;
}

And If you want to debug, macros are harder to debug the flow with a debugger, so I've used a python script to generate the expansion of the macro to source and header files before compile (I build with scons which is python based so I automated the source files generation)

thakee nathees
  • 877
  • 1
  • 9
  • 16
  • wow, that's pretty hairy, but neat! Is that common to do in C? Also, why the use of the `##` ? – David542 Jan 30 '21 at 04:42
  • it's concatenation operator in macros (see: https://learn.microsoft.com/en-us/cpp/preprocessor/token-pasting-operator-hash-hash?view=msvc-160), and yeah, it's common in c (see: https://stackoverflow.com/questions/10950828/simulation-of-templates-in-c-for-a-queue-data-type). And I recommend not to try every aspect OOP in c. Write c in it's way – thakee nathees Jan 30 '21 at 10:53