Wie kann ich Polymorphismus im OO-Stil in C simulieren?

Lesezeit: 7 Minuten

Benutzeravatar von prinzdezibel
prinzdezibel

Gibt es eine Möglichkeit, OO-ähnlichen Code in die C Programmiersprache?


Siehe auch:

  • Können Sie objektorientierten Code in C schreiben?
  • Objektorientierung in C

Gefunden durch Suche auf “[c] oo”.

  • _Generic kann für den Polymorphismus zur Kompilierzeit verwendet werden. __attribute__((cleanup ())) kann für RAII verwendet werden. Und Fehlercodes sind der Ausnahmebehandlung überlegen.

    – jha-G

    27. August 2021 um 9:24 Uhr

Der erste C++-Compiler (“C mit Klassen”) würde tatsächlich C-Code generieren, also ist das definitiv machbar.

Grundsätzlich ist Ihre Basisklasse eine Struktur; Abgeleitete Strukturen müssen die Basisstruktur an der ersten Position enthalten, sodass ein Zeiger auf die “abgeleitete” Struktur auch ein gültiger Zeiger auf die Basisstruktur ist.

typedef struct {
   data member_x;
} base;

typedef struct {
   struct base;
   data member_y;
} derived;

void function_on_base(struct base * a); // here I can pass both pointers to derived and to base

void function_on_derived(struct derived * b); // here I must pass a pointer to the derived class

Die Funktionen können als Funktionszeiger Teil der Struktur sein, sodass eine Syntax wie p->call(p) möglich wird, aber Sie müssen immer noch explizit einen Zeiger auf die Struktur an die Funktion selbst übergeben.

  • Dies erklärt nicht, wie das Überschreiben von Methoden in C funktionieren würde. Wie können Sie function_on_base überschreiben, um auf die memeber_y von abgeleiteten zuzugreifen, wie Sie es in polymorphen C++-Aufrufen können?

    – Johannes K

    30. Oktober 2012 um 15:50 Uhr

  • Überschreiben ist in C nicht möglich.

    – Patrick Collins

    8. Januar 2014 um 15:40 Uhr

  • Diese Antwort ist falsch. Vorbei an a struct derived* zu function_on_base wird nicht kompiliert; struct derived* ist ein anderer Typ als struct base* auch wenn die Adresse korrekt ist; jedoch, wenn Sie den Zeiger von umwandeln derived* zu base*, wird es funktionieren (aber Sie werden die Typüberprüfung zur Kompilierzeit verpassen und stattdessen zur Laufzeit einen Absturz bekommen). @PatrickCollins Überschreiben ist in C möglich: pastebin.com/W5xEytbv

    – weberc2

    3. Juli 2014 um 18:36 Uhr


  • @JohnK Siehe Kommentar oben.

    – weberc2

    3. Juli 2014 um 18:48 Uhr

  • @weberc2 Du hast Recht, ich bin mir nicht sicher, was ich mir dabei gedacht habe, als ich das geschrieben habe. Ich hatte vielleicht “Überladung” im Sinn, was Ihr Einfügen auch vermuten lässt.

    – Patrick Collins

    4. Juli 2014 um 4:03 Uhr

Benutzeravatar von Peter Štibraný
Peter Stibraný

Der übliche Ansatz besteht darin, Struct mit Zeigern auf Funktionen zu definieren. Dies definiert “Methoden”, die für jeden Typ aufgerufen werden können. Untertypen legen dann ihre eigenen Funktionen in dieser gemeinsamen Struktur fest und geben sie zurück.

Zum Beispiel gibt es im Linux-Kernel struct:

struct inode_operations {
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    struct dentry * (*lookup) (struct inode *,struct dentry *, 
                               struct nameidata *);
    ...
};

Jeder registrierte Dateisystemtyp registriert dann seine eigenen Funktionen für create, lookup, und verbleibende Funktionen. Der Rest des Codes kann dann generische inode_operations verwenden:

struct inode_operations   *i_op;
i_op -> create(...);

  • Auf diese Weise hat cfront (der ursprüngliche C++-Compiler) C++ in C konvertiert, das dann mit pcc kompiliert wurde. Ich habe viel darüber gelernt, wie das funktioniert, wenn man mit Kerndateien aus diesem Durcheinander umgeht.

    – Paul Tomblin

    7. Februar 2009 um 16:30 Uhr

  • Dies ist kein Polymorphismus im C++-Stil. Ein großer Vorteil von Polymorphismus gegenüber Funktionszeigern besteht darin, dass Polymorphismus eine Datenbindung ermöglicht, während Funktionszeiger dies nicht tun. In diesem Fall gibt es keine Möglichkeit zur Datenbindung, es ist nur eine Struktur von Funktionszeigern. Außerdem denke ich, dass die Struktur in diesem Kommentar überflüssig ist.

    – Benutzer2445507

    25. Juni 2018 um 19:52 Uhr

Benutzeravatar von thinkbeforecoding
Denken Sie vor dem Codieren

C++ ist nicht so weit von C entfernt.

Klassen sind Strukturen mit einem versteckten Zeiger auf eine Tabelle von Funktionszeigern namens VTable. Die Vtable selbst ist statisch. Wenn Typen auf Vtables mit der gleichen Struktur zeigen, aber Zeiger auf andere Implementierungen zeigen, erhalten Sie Polymorphismus.

Es wird empfohlen, die Aufruflogik in Funktionen zu kapseln, die die Struktur als Parameter verwenden, um Code-Clutter zu vermeiden.

Sie sollten auch die Instantiierung und Initialisierung von Strukturen in Funktionen (dies entspricht einem C++-Konstruktor) und das Löschen (Destruktor in C++) kapseln. Das sind sowieso gute Übungen.

typedef struct
{
   int (*SomeFunction)(TheClass* this, int i);
   void (*OtherFunction)(TheClass* this, char* c);
} VTable;

typedef struct
{
   VTable* pVTable;
   int member;

} TheClass;

So rufen Sie die Methode auf:

int CallSomeFunction(TheClass* this, int i)
{
  (this->pVTable->SomeFunction)(this, i);
}

  • Das Beispiel ist unvollständig. Es gibt keine Erklärung dafür, wie man eine neue Klasse mit anderen Membern implementiert (außer nur int). In diesem Fall scheinen alle “polymorphen” Objekte dasselbe int-Mitglied zu haben. Was ist, wenn Sie Klassen mit unterschiedlichen Mitgliedern definieren möchten? Ich denke, in diesem Fall sollte Ihre Struktur einen void-Zeiger auf Datenmitglieder anstelle eines konkreten int haben.

    – Benutzer2445507

    25. Juni 2018 um 19:55 Uhr

  • @ user2445507 Das Beispiel implementiert eine vtable, die Funktionen zur Laufzeit aufruft, genau das, was C++ hinter den Kulissen tun würde, wenn Sie eine Klasse erstellen.

    – Niclas Larson

    3. Oktober 2018 um 8:00 Uhr

  • @NiclasLarsson In C++ ist es möglich, eine andere Klasse zu erstellen, die die reinen virtuellen Funktionen erbt, jedoch mit anderen Mitgliedsvariablen. Dann kann die in dieser Klasse implementierte virtuelle Funktion diese Mitgliedsvariablen verwenden. Dies ist ein Schlüsselaspekt des Polymorphismus. Das Beispiel zeigt nicht, wie das geht. Für dieses Beispiel können Sie auch einfach den Funktionszeiger verwenden.

    – Benutzer2445507

    5. Oktober 2018 um 19:29 Uhr

Ich habe mir die Antworten aller anderen angesehen und bin zu folgendem Ergebnis gekommen:

#include <stdio.h>

typedef struct
{
    int (*get)(void* this);
    void (*set)(void* this, int i);
    int member;

} TheClass;

int Get(void* this)
{
    TheClass* This = (TheClass*)this;
    return This->member;
}

void Set(void* this, int i)
{
    TheClass* This = (TheClass*)this;
    This->member = i;
}

void init(TheClass* this)
{
    this->get = &Get;
    this->set = &Set;
}

int main(int argc, char **argv)
{
    TheClass name;
    init(&name);
    (name.set)(&name, 10);
    printf("%d\n", (name.get)(&name));
    return 0;
}

Ich hoffe das beantwortet einige Fragen.

Benutzeravatar von Sébastien RoccaSerra
Sébastien Rocca Serra

Anhang B des Artikels Öffnen Sie wiederverwendbare Objektmodellevon Ian Piumarta und Alessandro Warth von VPRI ist eine Implementierung eines Objektmodells in GNU C, etwa 140 Codezeilen. Es ist eine faszinierende Lektüre!

Hier ist die nicht zwischengespeicherte Version des Makros, das Nachrichten an Objekte sendet, indem es eine GNU-Erweiterung für C verwendet (Aussage Ausdruck):

struct object;

typedef struct object *oop; 
typedef oop *(*method_t)(oop receiver, ...);

//...

#define send(RCV, MSG, ARGS...) ({ \ 
    oop r = (oop)(RCV); \ 
    method_t method = _bind(r, (MSG)); \ 
    method(r, ##ARGS); \ 
}) 

Sehen Sie sich im selben Dokument die object, vtable, vtable_delegated und symbol Strukturen und die _bind und vtable_lookup Funktionen.

Prost!

Was ich normalerweise gerne mache, ist, die Strukturen in eine andere zu verpacken, die Metainformationen über die verpackte Klasse enthält, und dann besucherähnliche Funktionslisten zu erstellen, die auf die generische Struktur wirken. Der Vorteil dieses Ansatzes besteht darin, dass Sie die vorhandenen Strukturen nicht ändern müssen und Besucher für jede Teilmenge von Strukturen erstellen können.

Nehmen Sie das übliche Beispiel:

typedef struct {
    char call[7] = "MIAO!\n";
} Cat;
    
typedef struct {
    char call[6] = "BAU!\n";
} Dog;

Wir können die 2 Strukturen in diese neue Struktur einpacken:

typedef struct {
    const void * animal;
    AnimalType type;
} Animal;

Der Typ kann ein einfaches int sein, aber seien wir nicht faul:

typedef enum  {
    ANIMAL_CAT = 0,
    ANIMAL_DOG,
    ANIMAL_COUNT
} AnimalType;

Es wäre schön, einige Wrapping-Funktionen zu haben:

Animal catAsAnimal(const Cat * c) {
    return (Animal){(void *)c, ANIMAL_CAT};
}

Animal dogAsAnimal(const Dog * d) {
    return (Animal){(void *)d, ANIMAL_DOG};
}

Jetzt können wir unseren “Besucher” definieren:

void catCall ( Animal a ) {
    Cat * c = (Cat *)a.animal;
    printf(c->call);
}

void dogCall ( Animal a ) {
    Dog * d = (Dog *)a.animal;
    printf(d->call);
}

void (*animalCalls[ANIMAL_COUNT])(Animal)={&catCall, &dogCall};

Dann wird die tatsächliche Verwendung sein:

Cat cat;
Dog dog;

Animal animals[2];
animals[0] = catAsAnimal(&cat);
animals[1] = dogAsAnimal(&dog);

for (int i = 0; i < 2; i++) {
    Animal a = animals[i];
    animalCalls[a.type](a);
}

Der Nachteil dieses Ansatzes besteht darin, dass Sie die Strukturen jedes Mal umschließen müssen, wenn Sie sie als generischen Typ verwenden möchten.

Benutzeravatar von Priyank Bolia
Priyank Bolia

Die Dateifunktionen fopen, fclose, fread sind Beispiele für OO-Code in C. Anstelle der privaten Daten in der Klasse arbeiten sie an der FILE-Struktur, die zum Einkapseln der Daten verwendet wird, und die C-Funktionen fungieren als Member-Klassenfunktionen. http://www.amazon.com/File-Structures-Object-Oriented-Approach-C/dp/0201874016

  • Das Buch, auf das verwiesen wird, ist nur C++. Hinweis: Ich habe eine Kopie und würde es heute nicht empfehlen. Wenn ich heute eine OO-Empfehlung in C hätte, wäre es C Interfaces and Implementations: Techniques for Creating Reusable Software von David Hanson (amzn.com/0201498413). Ein absolut brillantes Buch, und die meisten Programmierer täten gut daran, es zu verstehen, die meisten Beispiele darin stammen von Compiler-Backends, daher ist der Code beispielhaft.

    – Harry

    26. August 2014 um 17:38 Uhr


1418280cookie-checkWie kann ich Polymorphismus im OO-Stil in C simulieren?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy