Was wäre eine Reihe von raffinierten Präprozessor-Hacks (ANSI C89 / ISO C90-kompatibel), die eine hässliche (aber verwendbare) Objektorientierung in C ermöglichen?
Ich bin mit einigen verschiedenen objektorientierten Sprachen vertraut. Antworten Sie daher bitte nicht mit Antworten wie "Learn C ++!". Ich habe " Objektorientierte Programmierung mit ANSI C " (Vorsicht: PDF-Format ) und einige andere interessante Lösungen gelesen , aber ich interessiere mich hauptsächlich für Ihre :-)!
Siehe auch Können Sie objektorientierten Code in C schreiben?
Antworten:
C Object System (COS) klingt vielversprechend (es ist immer noch in der Alpha-Version). Aus Gründen der Einfachheit und Flexibilität wird versucht, die verfügbaren Konzepte auf ein Minimum zu beschränken: einheitliche objektorientierte Programmierung, einschließlich offener Klassen, Metaklassen, Eigenschaftsmetaklassen, Generika, Multimethoden, Delegierung, Eigentum, Ausnahmen, Verträge und Schließungen. Es gibt einen Entwurf (PDF), der dies beschreibt.
Ausnahme in C ist eine C89-Implementierung des TRY-CATCH-ENDLICH in anderen OO-Sprachen. Es kommt mit einer Testsuite und einigen Beispielen.
Beide von Laurent Deniau, an dem viel gearbeitet wird OOP in C arbeitet .
quelle
Ich würde von der Verwendung von Präprozessoren (ab) abraten, um zu versuchen, die C-Syntax der einer anderen objektorientierteren Sprache ähnlicher zu machen. Auf der einfachsten Ebene verwenden Sie einfach einfache Strukturen als Objekte und geben sie durch Zeiger weiter:
Um Dinge wie Vererbung und Polymorphismus zu bekommen, muss man etwas härter arbeiten. Sie können die manuelle Vererbung durchführen, indem Sie das erste Mitglied einer Struktur als Instanz der Oberklasse festlegen und dann Zeiger auf Basisklassen und abgeleitete Klassen frei umwandeln:
Um Polymorphismus (dh virtuelle Funktionen) zu erhalten, verwenden Sie Funktionszeiger und optional Funktionszeigertabellen, die auch als virtuelle Tabellen oder vtables bezeichnet werden:
Und so macht man Polymorphismus in C. Es ist nicht schön, aber es macht den Job. Es gibt einige Probleme mit Zeigerumwandlungen zwischen Basis- und abgeleiteten Klassen, die sicher sind, solange die Basisklasse das erste Mitglied der abgeleiteten Klasse ist. Die Mehrfachvererbung ist viel schwieriger. In diesem Fall müssen Sie Ihre Zeiger manuell anpassen, um zwischen anderen Basisklassen als der ersten zu wechseln. Dies ist sehr schwierig und fehleranfällig.
Eine andere (knifflige) Sache, die Sie tun können, ist, den dynamischen Typ eines Objekts zur Laufzeit zu ändern! Sie weisen ihm einfach einen neuen vtable-Zeiger zu. Sie können sogar einige der virtuellen Funktionen selektiv ändern, während andere beibehalten werden, wodurch neue Hybridtypen erstellt werden. Achten Sie nur darauf, eine neue vtable zu erstellen, anstatt die globale vtable zu ändern. Andernfalls wirken Sie sich versehentlich auf alle Objekte eines bestimmten Typs aus.
quelle
struct derived {struct base super;};
ist offensichtlich zu erraten, wie es funktioniert, da es durch die Bytereihenfolge korrekt ist.Ich habe einmal mit einer C-Bibliothek gearbeitet, die auf eine Weise implementiert wurde, die mir sehr elegant erschien. Sie hatten in C eine Möglichkeit geschrieben, Objekte zu definieren und dann von ihnen zu erben, sodass sie so erweiterbar waren wie ein C ++ - Objekt. Die Grundidee war folgende:
Das Erben ist schwer zu beschreiben, aber im Grunde war es das Folgende:
Dann in einer anderen Datei:
Dann könnten Sie einen Van im Speicher erstellen lassen, der von Code verwendet wird, der nur über Fahrzeuge Bescheid weiß:
Es hat wunderbar funktioniert und die .h-Dateien haben genau definiert, was Sie mit jedem Objekt tun können sollen.
quelle
Der GNOME-Desktop für Linux ist in objektorientiertem C geschrieben und verfügt über ein Objektmodell namens " GObject ", das Eigenschaften, Vererbung, Polymorphismus sowie einige andere Extras wie Referenzen, Ereignisbehandlung (als "Signale" bezeichnet) und Laufzeit unterstützt Eingabe, private Daten usw.
Es enthält Präprozessor-Hacks, mit denen Sie beispielsweise in der Klassenhierarchie typisieren usw. Hier ist eine Beispielklasse, die ich für GNOME geschrieben habe (Dinge wie gchar sind typedefs):
Klassenquelle
Klassenkopf
Innerhalb der GObject-Struktur gibt es eine GType-Ganzzahl, die als magische Zahl für das dynamische Typisierungssystem von GLib verwendet wird (Sie können die gesamte Struktur in einen "GType" umwandeln, um den Typ zu finden).
quelle
Ich habe so etwas in C gemacht, bevor ich wusste, was OOP ist.
Es folgt ein Beispiel, das einen Datenpuffer implementiert, der bei Bedarf bei einer minimalen Größe, einem Inkrement und einer maximalen Größe wächst. Diese spezielle Implementierung basierte auf "Elementen", dh sie wurde entwickelt, um eine listenartige Sammlung eines beliebigen C-Typs zu ermöglichen, nicht nur einen Byte-Puffer variabler Länge.
Die Idee ist, dass das Objekt mit xxx_crt () instanziiert und mit xxx_dlt () gelöscht wird. Jede der "Mitglied" -Methoden benötigt einen speziell eingegebenen Zeiger, um damit zu arbeiten.
Auf diese Weise habe ich eine verknüpfte Liste, einen zyklischen Puffer und eine Reihe anderer Dinge implementiert.
Ich muss gestehen, ich habe nie darüber nachgedacht, wie Vererbung mit diesem Ansatz implementiert werden kann. Ich stelle mir vor, dass eine Mischung aus dem von Kieveli angebotenen ein guter Weg sein könnte.
dtb.c:
dtb.h
PS: vint war einfach ein typedef von int - ich habe es verwendet, um mich daran zu erinnern, dass seine Länge von Plattform zu Plattform (für die Portierung) variabel war.
quelle
Etwas abseits des Themas, aber der ursprüngliche C ++ - Compiler Cfront kompilierte C ++ zu C und dann zu Assembler.
Konservierte hier .
quelle
Wenn Sie Methoden, die für Objekte aufgerufen werden, als statische Methoden betrachten, die ein implizites '
this
' an die Funktion übergeben, kann dies das Denken von OO in C erleichtern.Beispielsweise:
wird:
Oder etwas ähnliches.
quelle
string->length(s);
ffmpeg (ein Toolkit für die Videoverarbeitung) ist in reinem C (und Assemblersprache) geschrieben, verwendet jedoch einen objektorientierten Stil. Es ist voll von Strukturen mit Funktionszeigern. Es gibt eine Reihe von Factory-Funktionen, die die Strukturen mit den entsprechenden "Methoden" -Zeigern initialisieren.
quelle
Wenn Sie wirklich catefully, auch Standard - C - Bibliothek verwendet OOP denken - prüfen ,
FILE *
als Beispiel:fopen()
initialisiert einFILE *
Objekt, und verwenden Sie es verwendet , Mitglied Methodenfscanf()
,fprintf()
,fread()
,fwrite()
und andere, und schließlich zum Abschluss bringt es mitfclose()
.Sie können auch den Pseudo-Objective-C-Weg wählen, der ebenfalls nicht schwierig ist:
Benutzen:
Dies kann sich aus einem solchen Objective-C-Code ergeben, wenn ein ziemlich alter Objective-C-zu-C-Übersetzer verwendet wird:
quelle
__attribute__((constructor))
man invoid __meta_Foo_init(void) __attribute__((constructor))
?popen(3)
gibt auch einFILE *
für ein anderes Beispiel zurück.Ich denke, was Adam Rosenfield gepostet hat, ist die richtige Art, OOP in C zu machen. Ich möchte hinzufügen, dass das, was er zeigt, die Implementierung des Objekts ist. Mit anderen Worten, die eigentliche Implementierung würde in die
.c
Datei eingefügt, während die Schnittstelle in die Header-.h
Datei eingefügt würde . Verwenden Sie zum Beispiel das obige Affenbeispiel:Die Schnittstelle würde folgendermaßen aussehen:
Sie können in der Schnittstellendatei sehen
.h
, dass Sie nur Prototypen definieren. Anschließend können Sie den Implementierungsteil ".c
Datei" in eine statische oder dynamische Bibliothek kompilieren . Dadurch wird eine Kapselung erstellt, und Sie können die Implementierung nach Belieben ändern. Der Benutzer Ihres Objekts muss fast nichts über die Implementierung wissen. Dies konzentriert sich auch auf das Gesamtdesign des Objekts.Ich bin der persönlichen Überzeugung, dass oop eine Möglichkeit ist, Ihre Codestruktur und Wiederverwendbarkeit zu konzipieren, und wirklich nichts mit den anderen Dingen zu tun hat, die zu C ++ hinzugefügt werden, wie Überladen oder Vorlagen. Ja, das sind sehr nette nützliche Funktionen, aber sie sind nicht repräsentativ dafür, was objektorientierte Programmierung wirklich ist.
quelle
typedef struct Monkey {} Monkey;
Was bringt es, sie nach ihrer Erstellung zu tippen?struct _monkey
ist einfach ein Prototyp. Die tatsächliche Typdefinition wird in der Implementierungsdatei (der C-Datei) definiert. Dies erzeugt den Kapselungseffekt und ermöglicht es dem API-Entwickler, die Affenstruktur in Zukunft neu zu definieren, ohne die API zu ändern. Benutzer der API müssen sich nur mit den tatsächlichen Methoden befassen. Der API-Designer kümmert sich um die Implementierung, einschließlich der Anordnung des Objekts / der Struktur. Daher sind die Details des Objekts / der Struktur vor dem Benutzer verborgen (ein undurchsichtiger Typ).int getCount(ObjectType obj)
usw. auf Ihre Mitglieder zugreifen, wenn Sie die Struktur in der Implementierungsdatei definieren.Meine Empfehlung: Halte es einfach. Eines der größten Probleme, das ich habe, ist die Wartung älterer Software (manchmal über 10 Jahre alt). Wenn der Code nicht einfach ist, kann es schwierig sein. Ja, man kann sehr nützliches OOP mit Polymorphismus in C schreiben, aber es kann schwierig zu lesen sein.
Ich bevorzuge einfache Objekte, die eine genau definierte Funktionalität enthalten. Ein gutes Beispiel hierfür ist GLIB2 , zum Beispiel eine Hash-Tabelle:
Die Schlüssel sind:
quelle
Wenn ich OOP in CI schreiben würde, würde ich wahrscheinlich ein Pseudo- Pimpl- Design verwenden. Anstatt Zeiger an Strukturen zu übergeben, übergeben Sie am Ende Zeiger an Zeiger an Strukturen. Dies macht den Inhalt undurchsichtig und erleichtert Polymorphismus und Vererbung.
Das eigentliche Problem mit OOP in C ist, was passiert, wenn Variablen den Gültigkeitsbereich verlassen. Es gibt keine vom Compiler generierten Destruktoren, was zu Problemen führen kann. Makros können möglicherweise helfen, aber es wird immer hässlich anzusehen sein.
quelle
if
Anweisungen verwende und sie am Ende freigebe. Zum Beispielif ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }
Eine andere Möglichkeit, mit C in einem objektorientierten Stil zu programmieren, besteht darin, einen Codegenerator zu verwenden, der eine domänenspezifische Sprache in C umwandelt. Wie bei TypeScript und JavaScript, um OOP auf js zu bringen.
quelle
Ausgabe:
Hier ist eine Show, was OO-Programmierung mit C ist.
Dies ist echtes, reines C, keine Präprozessormakros. Wir haben Vererbung, Polymorphismus und Datenkapselung (einschließlich Daten, die für Klassen oder Objekte privat sind). Es gibt keine Chance für ein geschütztes Qualifikationsäquivalent, dh private Daten sind auch in der Vererbungskette privat. Dies ist jedoch keine Unannehmlichkeit, da ich dies nicht für notwendig halte.
CPolygon
wird nicht instanziiert, weil wir es nur verwenden, um Objekte entlang der Vererbungskette zu manipulieren, die gemeinsame Aspekte haben, diese aber unterschiedlich implementieren (Polymorphismus).quelle
@Adam Rosenfield hat eine sehr gute Erklärung, wie man mit C OOP erreicht
Außerdem würde ich Ihnen empfehlen, zu lesen
1) pjsip
Eine sehr gute C-Bibliothek für VoIP. Sie können lernen, wie OOP durch Strukturen und Funktionszeigertabellen erreicht wird
2) iOS-Laufzeit
Erfahren Sie, wie iOS Runtime Ziel C unterstützt. Es erreicht OOP durch einen Zeiger, eine Metaklasse
quelle
Für mich sollte die Objektorientierung in C folgende Merkmale haben:
Kapselung und Ausblenden von Daten (kann mithilfe von Strukturen / undurchsichtigen Zeigern erreicht werden)
Vererbung und Unterstützung des Polymorphismus (Einzelvererbung kann mithilfe von Strukturen erreicht werden - stellen Sie sicher, dass die abstrakte Basis nicht instanziierbar ist)
Konstruktor- und Destruktorfunktionalität (nicht leicht zu erreichen)
Typprüfung (zumindest für benutzerdefinierte Typen, da C keine erzwingt)
Referenzzählung (oder etwas, um RAII zu implementieren )
Eingeschränkte Unterstützung für die Ausnahmebehandlung (setjmp und longjmp)
Darüber hinaus sollte es sich auf ANSI / ISO-Spezifikationen und nicht auf compilerspezifische Funktionen stützen.
quelle
Schauen Sie sich http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html an . Wenn nichts anderes die Dokumentation durchliest, ist dies eine aufschlussreiche Erfahrung.
quelle
Ich bin ein bisschen zu spät zur Party hier, aber ich vermeide gerne beide Makro-Extreme - zu viele oder zu viele verschleierte Codes, aber ein paar offensichtliche Makros können die Entwicklung und das Lesen des OOP-Codes erleichtern:
Ich denke, dies hat eine gute Balance, und die Fehler, die es (zumindest mit den Standardoptionen von gcc 6.3) für einige der wahrscheinlicheren Fehler erzeugt, sind hilfreich, anstatt zu verwirren. Der springende Punkt ist, die Produktivität der Programmierer zu verbessern, nicht wahr?
quelle
Wenn Sie einen kleinen Code schreiben müssen, versuchen Sie Folgendes: https://github.com/fulminati/class-framework
quelle
Ich arbeite auch daran basierend auf einer Makrolösung. Es ist also nur für die Mutigsten, denke ich ;-) Aber es ist schon ganz nett und ich arbeite bereits an ein paar Projekten darüber. Es funktioniert so, dass Sie zuerst eine separate Header-Datei für jede Klasse definieren. So was:
Um die Klasse zu implementieren, erstellen Sie eine Header-Datei und eine C-Datei, in der Sie die folgenden Methoden implementieren:
In den Header, den Sie für die Klasse erstellt haben, fügen Sie andere benötigte Header hinzu und definieren Typen usw., die sich auf die Klasse beziehen. Sowohl im Klassenkopf als auch in der C-Datei enthalten Sie die Klassenspezifikationsdatei (siehe erstes Codebeispiel) und ein X-Makro. Diese X-Makros ( 1 , 2 , 3 usw.) erweitern den Code auf die tatsächlichen Klassenstrukturen und andere Deklarationen.
Um eine Klasse zu erben,
#define SUPER supername
und hinzufügensupername__define \
als erste Zeile in die Klassendefinition ein. Beide müssen da sein. Es gibt auch JSON-Unterstützung, Signale, abstrakte Klassen usw.Um ein Objekt zu erstellen, verwenden Sie einfach
W_NEW(classname, .x=1, .y=2,...)
. Die Initialisierung basiert auf der in C11 eingeführten Strukturinitialisierung. Es funktioniert gut und alles, was nicht aufgeführt ist, wird auf Null gesetzt.Verwenden Sie zum Aufrufen einer Methode
W_CALL(o,method)(1,2,3)
. Es sieht aus wie ein Funktionsaufruf höherer Ordnung, ist aber nur ein Makro. Es erweitert sich,((o)->klass->method(o,1,2,3))
was ein wirklich schöner Hack ist.Siehe Dokumentation und den Code selbst .
Da das Framework Boilerplate-Code benötigt, habe ich ein Perl-Skript (Wobject) geschrieben, das den Job erledigt. Wenn Sie das verwenden, können Sie einfach schreiben
und es werden die Klassenspezifikationsdatei, der Klassenkopf und eine C-Datei erstellt, die enthält,
Point_impl.c
wo Sie die Klasse implementieren. Es spart ziemlich viel Arbeit, wenn Sie viele einfache Klassen haben, aber immer noch alles in C. Wobject ist ist ein sehr einfacher Scanner auf der Basis regulärer Ausdrücke, der sich leicht an bestimmte Anforderungen anpassen oder von Grund auf neu schreiben lässt.quelle
Das Open-Source-Dynace-Projekt macht genau das. Es ist unter https://github.com/blakemcbride/Dynace
quelle
Sie können COOP ausprobieren , ein programmiererfreundliches Framework für OOP in C, das Klassen, Ausnahmen, Polymorphismus und Speicherverwaltung (wichtig für eingebetteten Code) enthält. Es ist eine relativ leichte Syntax, siehe das Tutorial im Wiki .
quelle