Oop In Cee
 
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern

Veränderung (letzte Korrektur) (Änderung, Normalansicht)

Verändert: 320c320
KategorieCee KategorieOop
KategorieC KategorieCee KategorieOop

Kann man in C objektorientiert Programmieren?
Ja, aber es ist mehr Arbeit und alle Aspekte von OO Sprachen kann man nicht nachbilden.

Warum sollte es dann jemand tun wollen?
Weil man die Vorteile von C (Systemnähe, Effizienz, ...) nicht aufgeben, aber möglichst vielen Vorteilen der OO Arbeitsweise und Denkweise haben möchte. Außerdem gibt es zusätzliche Vorteile, die OO Sprachen nicht haben.


siehe auch:

Vorteile von OopInCee:

Nachteile von OopInCee:
Es kann natürlich verschiedene Ausprägungen von OO Programmierung in C geben, und ich beschreibe hier nur eine, relativ einfache Form. -- HelmutLeitner
Siehe auch einzelne Punkte von ProgrammierStilAlpha
Objekte

C hat keine Objekte, also muss man die Verbindung von Datenstruktur und Funktionen nachbilden. Dazu vereinheitlicht man die Schnittstellen:

Zusätzlich sammelt man alle Funktionen zu einem Objekt in einem Sourcemodul und alle Prototypen in einem korrespondierenden Header (es kann Gründe geben, von dieser Organisation abzuweichen).

Die typischen Basisfunktionen für ein Objekt sind:

object=ObjectCreate(...);
ObjectSetProperty(object,property);
ObjectGetProperty(object,&property); 
property=ObjectRetProperty(object); 
error=ObjectAction(object); 
error=ObjectActionAnotherobject(object,anotherobject); 
...
ObjectFree(&object);

Das Vokabular ist natürlich frei wählbar, man könnte statt ObjectCreate auch object_create, ObjectNew, object_new oder object_alloc verwenden, aber es muss konsistent sein.

Speicherverwaltung

Die Speicherverwaltung erfolgt manuell, d. h. der Programmierer hat die Verantwortung für die Freigabe der erzeugten Objekte. Dabei gibt es im wesentlich 3 Möglichkeiten (2 davon sind einfach):

Das Standardbeispiel sieht so aus:

object=NULL;
...
object=ObjectCreate(...);  
if(object==NULL) {
   ...Fehlerbehandlung...
}
...
ObjectFree(&object);

Alle Freigabefunktionen müssen tolerant sein, das heißt: ObjectFree muss damit rechnen, dass ihm ein NULL-Object übergeben wird (und dann nichts tun). Weiter muss ObjectFree den Objektzeiger auf NULL setzen. D. h. es ist das Ziel dass alle Zeigervariablen korrekt sind, d. h. entweder den Wert NULL haben oder auf einen gültigen Speicherblock zeigen.

Auf diese Weise sind die Elemente voneinander ziemlich unabhängig und folgendes:

  object=NULL;
  ...
  for(;;) {
    switch(RandomRetIntRange(0,100)) {
      case 0: 
        goto do_finalize;
      case 1: case 14: case 77:
        ObjectFree(&object);
        break; 
      default:
        ObjectFree(&object);
        object=ObjectCreate(...);
        break;         
    }         
  }
  ...
do_finalize:
  ObjectFree(&object);

wäre völlig problemlos in Bezug auf die korrekte Speicherverwaltung.

Vererbung

Da es in C keinen Vererbungsmechanismus gibt, muss Vererbung - wenn sie gewünscht ist - simuliert werden. Im Bereich der Datenstrukturen ist die Integration einer "Parent"-Struktur relativ einfach:

typedef struct Parent {
  ...
} Parent;

typedef struct Child {
  Parent *parent;
  ....
} Child;

Bei den Methoden kann die Vererbung durch "Delegation" simuliert werden:

... ChildAction(Child *child) {
  return ParentAction(child->parent);
}   

Vererbung ist auf diese Art relativ arbeitsintensiv. Daher wird man sie auf des absolut notwendige Minimum beschränken. Also isolierte, mächtige Objekte gegenüber komplexen Klassenhierarchien bevorzugen.

Virtuelle Funktionen können auch mit Hilfe von Funktionspointern realisiert werden.

Polymorphie

Polymorphie kann, wenn sie benötigt wird (selten), mit verschiedenartigen Mitteln realisiert werden:

Kapselung

Eine Kapselung kann im strengen Sinn nicht realisiert werden, denn in C kann man mit einem Zeiger im Prinzip überall hineinarbeiten.

Eine Kapselung könnte in gewissen Ausmaß realisiert werden, indem die Übergabe der Objekte mittels neutralisierter Pointer (z. B. void Pointer) erfolgt, die nicht die eigentliche Strukturinformation haben.

Auf eine Kapselung wird verzichtet, weil sie für den C-Programmierer keine sinnvolle Kosten-Nutzen-Relation hat. Er muss in so vielen Bereichen diszipliniert sein, dass dies im Bereich der Objekte überhaupt kein zusätzliches Problem erzeugt.

Details

Gültigkeit von Objekt-Zeigervariablen

Ein wichtiger Punkt ist die Gültigkeit von Objekt-Zeigervariablen. Die Funktion, die ein Objekt erzeugt seit auch für die Fehlerkontrolle (Zeiger = NULL) zuständig. Außer der Freigabefunktion müssen sich alle anderen Funktionen darauf verlassen können, dass sie gültige Objekte übergeben bekommen.

Anmerkung: es wäre nicht effizient, würden dutzende Funktionen in einem Modul (einer "Klasse") redundant immer wieder die Korrektheit des gleichen Objektzeigers prüfen, wenn dies an einer Stelle, dort wo das Objekt erzeugt wird, zentral einmal geprüft und - im Fehlerfall - optimal rückgemeldet werden kann.

Fehlerbehandlung

Die Art der Fehlerbehandlung ist nicht essentiell für OopInCee, aber rundet die Sache ab. Die Rückmeldung erfolgt nur auf 3 Arten:

Die Fehlerreaktion erfolgt mittels goto so, dass flache Funktionen entstehen:

  ...
  error=ObjectAction(object);
  if(error) {
    ...Fehlerbehandlung...
    goto do_finalize;
  }
  ...
  error=AnobjectAction(anobject);
  if(error) {
    ...Fehlerbehandlung...
    goto do_finalize;
  }
  ...
do_finalize:
  ObjectFree(&object)
  AnobjectFree(&anobject)
  ... Finalisierung weiterer Objekte

Auf diese Weise bleiben die eigentlichen Aktivitäten einer Funktion auf der Hauptlinie und die Verschiebbarkeit von Aktivitäten wird nicht durch Einrückungsebenen behindert.


Beispiel

siehe OopInCeeBeispiel

Variante StackObjekte

Speicherverwaltung

Eine Funktion wie ObjectCreate(...) erzeugt die Objektdaten immer auf dem Heap. Damit man Objekte auch auf dem Stack ablegen kann, wäre eine Initialize-Funktion vielleicht besser:

Heap

Object* pObject=NULL;
...
pObject=malloc(sizeof(Object));

if(pObject==NULL) {
   ...Fehlerbehandlung...
}

ObjectInitialize(pObject, ...);  

...

ObjectCleanup(pObject);
free(pObject);
pObject=NULL; /* Kann man in Makro zusammenfassen */

Stack

Object object;
...
ObjectInitialize(&object, ...);  

...

ObjectCleanup(&object);

-- MichaelButscher

Vorteile:

Nachteile: Im Grunde ist das der Unterschied zwischen C++ und Java. C++ kann beide Arten von Objekten. Java kann nur Heapobjekte und ist dadurch einfacher.

In OopInCee schlage ich vor, den einfacheren Weg zu gehen und auf Stack-Objekte zu verzichten. Performance-Vorteile ergeben sich bei einfachen Objekten, die sich auch als Strukturen ohne dynamischer Speicherverwaltung verstehen lassen.

-- HelmutLeitner


Diskussion

Aus OopInCee#Polymorphie:

Polymorphie kann, wenn sie benötigt wird (selten)...

Polymorphie ist meiner Meinung nach eine der wichtigsten Eigenschaften von OOP überhaupt! Gerade dadurch, dass ich nur wissen muss, dass ein Objekt eine bestimmte Nachricht versteht, und dieses selber dafür verantwortlich ist, angemessen darauf zu reagieren, wird ein OO-Design doch so flexibel und leicht erweiterbar! -- IljaPreuß

Na ja. OopInCee ist eben nur eine unvollständige Nachbildung. Polymorphie ist möglich, muss aber mit einem Mehr an Komplexität und Arbeitsaufwand, u.U. sogar mit Performanceverlusten bezahlt werden. Bei der Polymorphie stellt sich dann noch die Frage nach der Ausprägung der Polymorphie. Das was du beschreibst (Ein Objekt braucht nur eine bestimmte Nachricht zu verstehen) ist z.B. weder in Java noch C++ erfüllt (Bist du Smalltalker?). In C könnte ich mir zur Implementierung nur etwas Perl-ähnliches mit einer Hash-Tabelle pro Klasse mit den Methoden vorstellen. Oder vielleicht die versteckte, explizite Übergabe eines Interface-Methoden-Vektors mit dem Objektpointer. --hl

Wenn man mit "Nachricht" nur den Aufruf eines bestimmten Eintrags in der virtuellen Methodentabelle meint, dann kann C++ auch Nachrichten empfangen.
Das scheint mir sogar sicherer, da es keine zufällige Namensgleichheit wie in SpracheSmalltalk geben kann.
Java andererseits kann auch Methoden nach Namen aufrufen mittels Reflection. Dürfte allerdings nicht sehr effizient sein. --MichaelButscher

Eigentlich bin ich in der SpracheJava zuhause, lerne aber gerade die SpracheSmalltalk. Ich muss zugeben, dass mir die StatischeTypisierung? von Java bereits das eine oder andere Mal in die Quere gekommen ist. Nichtsdestotrotz erachte ich die CodeRefactorings TypenschlüsselDurchUnterklasseErsetzen? und TypenschlüsselDurchZustandOderStrategieErsetzen? als elementar für das Verständnis von OoDesign? - und Grundvoraussetzung dafür ist eben das Vorhandensein von Polymorphie. Unter diesem Aspekt möchte ich bezweifeln, dass mit OopInCee eine 'ballastfreie OO Programmierung möglich ist', dass es sich um ein 'sehr fluides System...' handelt oder dass man wirklich ein zutreffendes 'Verständnis für die innere Arbeitsweise von OO Sprachen' erhält. -- ip

In dieser Konzentration scheinen mir meine eigenen Formulierungen etwas dick aufgetragen. Trotzdem möchte ich meine Standpunkte verteidigen. Wir müssten nur aufpassen, nicht in irgendeine Form von PissingContest abzugleiten. Wenn ich sage OopInCee gibt einen Sinn, dann meine ich ausdrücklich nicht, dass C in irgendeinem generellen Sinn "besser" ist als OO Sprachen. -- hl


Ist eigentlich garantiert, daß in C eine struct, die mit den gleichen Elementen beginnt wie eine andere "kompatibel" ist? Also z. B.:

#include <stdio.h>

struct Base {
  int a;
  double b;
}

struct Derived {
  int a;
  double b;
  char* c;
}

int main(void) {
  struct Derived deri;
  struct Base* pBase;
  pBase=(struct Base*)&deri;

  deri.b=0.5;
  deri.a=2;

  printf("%i %f", pBase->a, pBase->b);

  return 0;
}

Das sollte etwa "2 0.5" ausgeben. --mb

Ja, das ist garantiert. Der Standard garantiert, dass die Reihenfolge der Strukturelemente nicht verändert wird. Das Alignment kann bei gleicher Feldreihenfolge nicht variieren (solange du das StrukturAlignment? nicht bewußt beeinflußt, indem du die Strukturen in verschiedene Compilationseinheiten gibst und allfällig vorhandene implemetierungsspezifische Alignment-Parameter unterschiedlich setzt). -- hl


Ich störe mich etwas an der formulierung ganz zu Beginn dieser Seite: [In C OO Programmieren] ist mehr Arbeit und alle Aspekte von OO Sprachen kann man nicht nachbilden. Dies stimmt natürlich nicht. Man kann alle Aspekte nachbilden. Dies sieht man schon daher das C TuringÄquivalent? ist und die ersten Compiler für C++ die Sprache nicht in Assembler sondern in C übersetzt haben. Wieso steht das denn so da? -- th

Theoretisch gesehen hast du vielleicht recht, aber praktisch funktionieren nur Dinge mit vertretbarem Arbeitsaufwand. Versuche z.B. in C Exceptions so zu implementieren, dass es sowohl von der Syntax her als auch von der Funktionalität her (Resourcenfreigabe der zwischen throw und catch liegenden Funktionen) äquivalent ist. Das ist kaum realisierbar. Wenn ich von OopInCee rede, dann möchte ich damit etwas Praktisches beschreiben, das mehr Nutzen bringt als es Arbeitsaufwand kostet. -- hl


KategorieC KategorieCee KategorieOop
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 29. November 2007 8:44 (diff))
Suchbegriff: gesucht wird
im Titel
im Text