Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
94 views
in Technique[技术] by (71.8m points)

c - Add additional "methods" for a subclass

This is a conceptual question to see how an OOP technique might be done in C. I know it's not practical or recommended and there are many languages that would work better for this, but I'm just seeing how it might be done as a beginner to C.

Let's say I have a base object called Thing. It will have some data and a few functions. Then I want to add another sub object called Alien -- it will have all the Thing data/methods, but also one additional method for it. Here is an example of what I have now:

#include<stdio.h>
#include<stdlib.h>

typedef struct VTable VTable;

typedef struct Thing {
    const VTable *vtable;
    char* name;
} Thing;

typedef struct VTable {
    void (*print)   (Thing* self);
} VTable;

void print_hello(Thing *self) {printf("Hello, %s", self->name);}

static const VTable thing_vtable = {
    .print = print_hello
};
typedef struct Alien {
    const VTable *vtable;
    char* name;
    // add one more function to vtable -- should that be a stand-alone? do a second 'subclass vtable'? etc.
} Alien;

void alien_function(void) {printf("Alien");}
Alien* init_alien(void)
{
    Alien* alien = malloc(sizeof(Alien));
    alien->vtable = &thing_vtable;
    /* alien->vtable->alien_function = alien_function; */
    return alien;
}

int main(void) {
    Alien *alien = init_alien();
    /* alien->vtable->alien_function(); */
    return 0;
}


Here is the code in Compiler Explorer.

What might be one way to add the 'extra methods' to the Alien type?

question from:https://stackoverflow.com/questions/66054278/add-additional-methods-for-a-subclass

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

In OP's example, struct Alien extends struct Thing and adds new "virtual" functions (in the sense of functions dynamically dispatched via vtables), or in other words the AlienVTable extends the base ThingVTable.

      struct Thing {                          struct Alien {
                                    ---      /---
/---      VTable *vtable;              |      |   VTable *vtable;       ----
|         char *name;                  | ---> |   char *name;               |
|                                   ---/      ---                          |
|     };                                          /* other Alien members*/  |
|                                             };                            |
|                                                                           |
|-->  struct ThingVTable {                    struct AlienVTable {      <---|
                                    ---      /---
        void (*print)(Thing *self);    | ---> |   void (*print)(Thing *self);
                                    ---/      ---
                                                  void (*function)(Alien *self);
                                                  /* other virtual Alien functions */
    };                                        };

The following is one way to implement this (comments refer to the rough C++ equivalent of some of the constructs, though the C code does not exactly duplicate the C++ semantics).

#ifdef _MSC_VER
#define _CRT_NONSTDC_NO_DEPRECATE // msvc strdup c4996
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct Thing {                                  // struct Thing {
    const struct ThingVTable *vtable;                   //
    char *name;                                         //     char *name;
} Thing;                                                //
                                                        //
typedef struct ThingVTable {                            //
    const void *base_vtable;                            //
    void (*print_name)(Thing *self);                    //     virtual void print_name();
    void (*print_age)(Thing *self);                     //     virtual void print_age() = 0;
} ThingVTable;                                          // };

void print_thing_name(Thing *self)                      // void Thing::print_name()
{   printf("Thing name: %s
", self->name); }           // { ... }

static const ThingVTable thing_vtable = {
    .base_vtable = NULL,
    .print_name = print_thing_name,
    .print_age = NULL
};

void construct_thing(Thing *self, const char *name)     // Thing::Thing(const char *name)
{                                                       // : name(name) { ... }
    self->vtable = &thing_vtable;                       //
    self->name = strdup(name);                          //
}                                                       //
                                                        //
void destruct_thing(Thing *self)                        // Thing::~Thing()
{                                                       // { ... }
    free(self->name);
    self->vtable = NULL;
}

Thing *new_thing(const char *name)                      // Thing *p = new Thing(name);
{                                                       //
    Thing *self = malloc(sizeof(Thing));                //
    if (self == NULL) return NULL;                      //
                                                        //
    construct_thing(self, name);                        //
    return self;                                        //
}                                                       //
                                                        //
void delete_thing(Thing *self)                          // delete p;
{
    destruct_thing(self);
    free(self);
}
typedef struct Alien {                                  // struct Alien : Thing {
    Thing super;                                        //
    int age;                                            //     int age;
} Alien;                                                //
                                                        //
typedef struct AlienVTable {                            //     void print_name() override;
    ThingVTable super;                                  //     void print_age() override;
    int (*is_et)(struct Alien *);                       //     virtual int is_et();
                                                        // };
} AlienVTable;

                                                        // override of base virtual function
void print_alien_name(Thing *self)                      // void print_name()
{   printf("Alien name: %s
", self->name); }           // { ... }
                                                        //
                                                        // implementation of base pure virtual function
void print_alien_age(Thing *self)                       // void print_age()
{   printf("Alien age: %d
", ((Alien *)self)->age); }  // { ... }
                                                        //
                                                        // new virtual function
int is_alien_et(Alien *self)                            // int is_alien()
{   return 0; }                                         // { ... }

static const AlienVTable alien_vtable = {
    .super.base_vtable = &thing_vtable,
    .super.print_name = print_alien_name,
    .super.print_age = print_alien_age,
    .is_et = is_alien_et
};

void construct_alien(Alien *self, const char *name, int age)
{
    construct_thing(&self->super, name);                // Alien::Alien(const char *name, int age)
    self->super.vtable = (ThingVTable *)&alien_vtable;  // : Thing(name),
    self->age = age;                                    //   age(age)
}                                                       //
                                                        //
void destruct_alien(Alien *self)                        // Alien::~Alien()
{                                                       // { ... }
    self->super.vtable = &thing_vtable;
    destruct_thing(&self->super);
}

Alien *new_alien(const char *name, int age)             // Alien *q = new Alien(name, age);
{                                                       //
    Alien *self = malloc(sizeof(Alien));                //
    if (self == NULL) return NULL;                      //
                                                        //
    construct_alien(self, name, age);                   //
    return self;                                        //
}                                                       //
                                                        //
void delete_alien(Alien *self)                          // delete q;
{
    destruct_alien(self);
    free(self);
}
int main(void) {
    Thing thing;                                        // not allowed in C++ since Thing is an abstract class
    construct_thing(&thing, "stack thing");             // Thing thing("stack thing");
    thing.vtable->print_name(&thing);                   // thing.print_name();
// error: pure virtual call
//  thing.vtable->print_age(&thing);                    // thing.print_age();
    destruct_thing(&thing);                             /* destructor implicitly called at end of main */

    printf("
");

    Alien *alien = new_alien("heap alien", 1234);                                    // Alien *alien = new Alien("heap alien", 1234)
    ((ThingVTable *)((AlienVTable *)alien->super.vtable)->super.base_vtable)->print_name((Thing *)alien);   // alien->Thing::print_name();
    ((AlienVTable *)alien->super.vtable)->super.print_name((Thing *)alien);          // alien->print_name();
    ((AlienVTable *)alien->super.vtable)->super.print_age((Thing *)alien);           // alien->print_age();
    printf("Is Alien ET? %d
", ((AlienVTable *)alien->super.vtable)->is_et(alien)); // alien->is_et();
    delete_alien(alien);                                                             // delete alien;

    printf("
");

    Thing *poly = (Thing *)new_alien("pointer to alien", 9786);                      // Thing *poly = new Alien("pointer to alien", 9786)
    poly->vtable->print_name(poly);                                                  // poly->print_name();
    poly->vtable->print_age(poly);                                                   // poly->print_age();
    printf("Is Alien ET? %d
", ((AlienVTable *)((Alien *)poly)->super.vtable)->is_et((Alien *)poly)); // poly->is_et();
    delete_alien((Alien *)poly);                                                     // delete poly;

    return 0;
}

Output:

Thing name: stack thing

Thing name: heap alien
Alien name: heap alien
Alien age: 1234
Is Alien ET? 0

Alien name: pointer to alien
Alien age: 9786
Is Alien ET? 0

Notes:

  • both Alien and AlienVTable mimic "inheritance" by embedding an instance of the base class as the first member (more about that at Struct Inheritance in C for example), which also legitimizes casts from pointers to a derived type to pointers to the base type;

  • the code could be made friendlier and easier to write/follow by using helper macros and inlines (or language extensions such as anonymous struct fields), but the intention here was to leave the internal mechanics fully exposed;

  • the destruct_ helpers would be prime candidates to make virtual and include in the vtable.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...