1

I am taking advantage of polymorphism in C by using virtual tables as described in Polymorphism (in C) and it works great.

Unfortunately, the limitation of my current project does not allow me to use function pointer or reference to structs in some part of my code. As a consequence, I cannot use the original approach directly.

In the mentioned approach, the base "class/struct" has a member that points to the virtual table. In order to get ride of this pointer, I decided to replace it with an enumerate that acts as key to access the virtual table.

It works but I wonder if if is the best solution. Do you come up with any alternative that fits better than my proposal?

/**
 * This example shows a common approach to achive polymorphism in C and an 
 * alternative that does NOT include a reference to function pointer in the 
 * base 
 * class.
 **/
#include<stdio.h>

//  some functions to make use of polymorphism
void funBase1()
{
    printf("base 1 \n");
}
void funBase2()
{
    printf("base 2 \n");
}
void funDerived1()
{
    printf("derived 1 \n");
}
void funDerived2()
{
    printf("derived 2 \n");
}


// struct to host virtual tables
typedef struct vtable {
    void (*method1)(void);
    void (*method2)(void);
}sVtable;

// enumerate to access the virtual table
typedef enum {BASE, DERIVED} eTypes;

// global virtual table used for the alternative solution
const sVtable g_vtableBaseAlternative[] = { 
    {funBase1, funBase2}, 
    {funDerived1, funDerived2},  };


// original approach that i cannot use
typedef struct base {
    const sVtable* vtable;
    int baseAttribute;
}sBase;

// alternative approach
typedef struct baseAlternative {
    const eTypes vtable_key;
    int baseAttribute;
}sBaseAlternative;


typedef struct derived {
    sBase base;
    int derivedAttribute;
}sDerived;

// original way to use 
static inline void method1(sBase* base)
{
    base->vtable->method1();
}

const sVtable* getVtable(const int key, const sVtable* vTableDic)
{
    return &vTableDic[key];
}

// Alternative to get a reference to the virtual table
static inline void method1Aternative(sBaseAlternative* baseAlternative)
{
    const sVtable* vtable;
    vtable = getVtable(baseAlternative->vtable_key, g_vtableBaseAlternative);
    printf("alternative version: ");
    vtable->method1();
}

int main() {

const sVtable vtableBase[] = { {funBase1, funBase2} };
const sVtable vtableDerived[] = { {funDerived1, funDerived2} };


sBase base = {vtableBase, 0 };
sBase derived = {vtableDerived, 1 };
sBaseAlternative baseAlternative = {DERIVED, 1 };

method1(&base);
method1(&derived);
method1Aternative(&baseAlternative);

}
  • I'd start thinking about whether polymorphism is worth the hassle. Many modern OOP projects use only composed objects and interfaces. Of course, doing the latter in C requires pointers as well ... –  Aug 03 '18 at 08:55
  • What you thought you know: [calling a function requires a function pointer](https://port70.net/~nsz/c/c11/n1570.html#6.5.2.2p1)... enjoy your language hacking ;) `const fun = {- C code /* -} Haskell code {- */ C code /* -} Haskell code {- */ C code // -}`... – autistic Aug 03 '18 at 09:24
  • I'm not sure why you can't use function pointers at all. I know certain coding guides disallow them in RAM. But if that's some Flash-based embedded MCU, you don't need to stoire them in RAM. And if not, you already run your code out of RAM. Either way, the system is dealing with function pointers under the hood already, so it's pointless to not use them explicitly in a safe way. – too honest for this site Aug 05 '18 at 19:39

2 Answers2

2

my current project does not allow me to use function pointer or reference to structs

You could use an array of T (any type you like) to represent a data type. For example, I tend to use arrays of unsigned char to serialise and deserialise my data structures for web transfer... Let's for example assume you're using sprintf and sscanf for serialisation and deserialisation (which you shouldn't really do, but they're okay for demos)... Instead of struct arguments, you use char * arguments, and you use sscanf to read that data to local variables, sprintf to modify it... that covers the no reference to structs allowed problem.

With regards to the function pointer problem, you could combine all of your functions into one which switches on... a tagged structure in string form... Here's a simple (yet incomplete) example involving two candidates for classes: a length-prefixed string which uses two bytes to encode the length and kind of derives from C-string behaviour, and a C string.

enum { fubar_is_string, fubar_is_length_prefixed_string };
typedef unsigned char non_struct_str_class;

size_t non_struct_strlen(non_struct_str_class *fubar) {
    size_t length = 0;
    switch (fubar++[0]) {
        case fubar_is_length_prefixed_string:
                              length = fubar++[0];
                              length <<= 8;
                              length += fubar++[0];
                              // carry through into the next case
                              // to support strings longer than 64KB
        case fubar_is_string: if (!length)
                                  length = strlen(fubar);
                              /* handle fubar as string */
    }
    return length;
}

C is a turing complete programming language, so of course it can be used to mimic object oriented polymorphism... but it's far better at mimicking procedural polymorphism, or in some cases even functional polymorphism... As an example, you could say qsort and bsearch use a primitive form of parametric polymorphism similar to that of map (even more similar to a filter idiom).

You could also use _Generic with limited success, for example as the C11 standard does by providing a generic cbrt macro for all of the standard floating point types:

#define cbrt(X) _Generic((X),                                      \
                        long double: cbrtl,                        \
                        default: cbrt,                             \
                        float: cbrtf                               \
                        )(X)

The preprocessor is particularly useful if you're going to go the route of mimicry... You might be interested in the C11 book by Klemens.

autistic
  • 1
  • 3
  • 35
  • 80
  • 1
    @P__J__ Out of curiousity, how do you think they debug the kernel? Stack dumps, like everything else, are a big clue... always... data representation doesn't change that fact. – autistic Aug 03 '18 at 08:38
  • 1
    @P__J__ How do you think they debug large programs in *any* programming language? The goto tool is the stackdump... and this approach of programming won't significantly impact upon your ability to use the stackdump to locate the area of fault. Your argument has just become "You'll struggle to debug this because it's not written in an OOL such as C++"... in a C question... not to mention, pop quiz time on "object oriented languages"... 1/ Which five principles apply to all OOP languages? 2/ Why can't you apply them in C? – autistic Aug 03 '18 at 09:52
  • @autistic thanks for the reply. It is an alternative I didn't come up with. However, if you were only limited to avoid function pointer in your "public" structs but you could use it in the private ones: would you use virtual table with some workaround like mine or just choose another alternative? – Juan Jiménez Aug 03 '18 at 10:36
  • @JuanJiménez I'm not sure what you're talking about, as there are *no* `struct`s in my code at all... are you referring to the character array I use instead of a `struct`? Yes, I use a single character from the character array to build kind of like a reverse *vtable* in the function (using the `switch`), rather than a vtable per object... – autistic Aug 03 '18 at 12:11
-3

I am taking advantage of polymorphism in C

you do not of course. You only create a prothesis of it. IMO the simulation of objects in C is the worst possible solution. If you prefer the OOP paradigm - use the OO language. In this case C++.

Answering your question - you can't do it (sane way) without the function pointers.

I discourage people from attempts of OOP like programming in the procedural languages. It usually leads to the less readable, error prone and very difficult to maintain programs.

Choose the correct tool (the language is the tool) for the task and the method.

It is like using the knife instead of screwdriver. You can, but the screwdriver will definitely be much better.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 1
    The comments on this answer have been deleted, as they had devolved into inappropriate behavior. **All users: before commenting here, please read the ["Be Nice"](http://stackoverflow.com/help/be-nice) policy and consider whether your comment is actually helpful.** – elixenide Aug 03 '18 at 13:30
  • 2
    Apparently the Linux kernel developers disagree with you, too. – too honest for this site Aug 05 '18 at 19:36
  • @toohonestforthissite Apparently Linus hates OO languages and C++ especially, so they did not have too much choice. http://harmful.cat-v.org/software/c++/linus This is not the best example. – 0___________ Aug 05 '18 at 20:43
  • But ironically they do some imitation of the so hated by Linus OOP. – 0___________ Aug 05 '18 at 21:03