Home-Produkte-Testarea-Kontakt-Datenschutz-Aktualisiert: 17-Jul-2009
< Voriger Tag   Nächster Tag >

Freitag, 17. Juli 2009

Lx0: Prototyp -- OOP in C

Nächster Schritt ist die Implementierung von OOP. Dazu habe ich mir gestern und vorgestern verschiedene Möglichkeiten für OOP in C angesehen und ausprobiert. Eine kompakte Lösung gibt es in Polymorphism in C -- andere lassen sich mit Stichworten wie "OOP in C", "Polymorphism in C" finden. Ich werde zunächst einmal eine pragmatische Lösung benutzen.

Die Stichworte hierfür sind Eigenschaften in struct, VTable und erster Parameter beim Methodenaufruf ist this.

Klasse in Lx0

public class X
  public uybte $x;

  
  public void constructor()
      $this.x := 1;
  end constructor

  
  public void classinfo()
      println $this.classname;
  end classinfo


  public void say()
      println "Say x";
  end say
endclass X;


$x = new X();
println "X x: ", $x.x;
$x.classinfo();
$x.say();

Klasse deklarieren

Ein Objekt besteht aus seinen Eigenschaften und Methoden. Wenn ein Objekt erzeugt wird, wird Speicher für seine Eigenschaften reserviert und Zeiger auf die Methoden gesetzt, die zum Objekt gehören. Jedes von einer Klasse erzeugte Objekt hat so einen eigenen Speicher für seine Eigenschaften und teilt sich dieselben Methoden mit allen anderen von dieser Klasse erzeugten Objekte. Dies wird mit structs umgesetzt:

typedef struct X_Attributes {      
    int x;
} X;


typedef struct X_Methods {
    void *super; // vtable of super class
    char *classname;
    void (*constructor) (X *this);
    void (*classinfo)   (X *this);
    void (*say)         (X *this);
} X_Methods;

Die Struktur mit den Methoden enthält Zeiger auf globale Funktionen, die sich dann alle Objekte teilen. Zusätzlich ist in dieser Struktur noch ein Zeiger auf die Superklasse sowie den Namen der Klasse enthalten (Klassenvariable).

Damit das Objekt weiß wo seine Methoden stehen, gibt es in seiner Struktur an erster Stelle einen Zeiger auf die VTable der Klasse:

typedef struct X_Attributes {
    struct X_Methods *vtable;

    int x;
} X;

Dazu kommen in der Headerdatei noch die Deklarationen für die globalen Funktionen in Form von Klassennamen_Methodenname() sowie ein extern für die VTable. Der komplette Quelltext steht in x_class.h.

void X_constructor(X *this);
void X_classinfo  (X *this);
void X_say        (X *this);
    
extern X_Methods X_vtable;

Klasse implementieren

Implementiert ist die Klasse in x_class.c. Am Anfang wird die VTable gefüllt:

X_Methods X_vtable = {
    NULL,          // No super class
    "X",
    X_constructor,
    X_classinfo,
    X_say
};

Darauf folgen die globalen Funktionen mit der Implementierung der Methoden:

void X_constructor(X *this) {
    printf("X constructor\n");
    this->x = 1;
}


void X_classinfo(X *this) {
    printf("Classinfo of %s\n", this->vtable->classname);
}


void X_say(X *this) {
    printf("Say x\n");
}

Benutzung

new() reserviert den Speicherbereich, der für die Eigenschaften benötigt wird, setzt den Zeiger auf die VTable und ruft dann den Konstruktor auf:

// Create new object
void * new(size_t size, void *vtable) {
    void * obj = calloc(1, size);
    assert(obj != NULL);

    ((X *) obj)->vtable = vtable;

    // Call constructor
    ((X *) obj)->vtable->constructor((void *) obj);

    
    return obj;
}

Die mit new() erzeugten Objekte können direkt benutzt werden. Das sieht durch die ->vtable und die Übergabe des Objekts selbst als ersten Parameter beim Methodenaufruf etwas seltsam aus aber das wird ja später alles vom Compiler generiert:

X *x = new(sizeof(X), &X_vtable);

printf("X x: %d\n", x->x);
x->vtable->classinfo(x);
x->vtable->say(x);

Vererbung

Die Klasse X wird nun um die Eigenschaft y und die Methode setY() erweitert. classinfo() wird von der Basisklasse übernommen und say() überschrieben:

public class Y
  public uybte $y;

  
  public void constructor()
      $this.y := 2;
  end constructor
  

  public void say()
      println "Say y";
  end say


  public void setY(ubyte $y)
      $this.y := $y;
  end setY
endclass Y;

Die Vererbung ist ganz pragmatisch implementiert, in dem für die Strukturen der Y_Klasse alle Elemente aus den X-Strukturen übernommen und die neuen Elemente angehängt werden:

typedef struct Y_Attributes {
    struct Y_Methods *vtable;

    int x; // Access x of super class
    int y; // New attribute
} Y;


typedef struct Y_Methods {
    X_Methods *super; // vtable of super class
    char *classname;
    void (*constructor) (Y *this);
    void (*classinfo)   (Y *this);
    void (*say)         (Y *this);
    void (*setY)        (Y *this, int y); // New method
} Y_Methods;

So kann von C aus leicht auf alle Attribute und Methoden zugegriffen werden.

Für das Überschreiben bekommt void (*say) einen Zeiger auf die neue globale Funktion void Y_say(Y *this) und für das Benutzen der void X_classinfo(X *this)-Methode aus der Basisklasse wird deren Zeiger in die Y_vtable eingetragen: y_class.c

Y_Methods Y_vtable = {
    &X_vtable, // vtable of super class
    "Y",
    Y_constructor,
    X_classinfo, // Use method of super class
    Y_say, // Overwrite
    Y_setY // New method
};

Eine Unterscheidung zwischen privaten und öffentlichen Eigenschaften und Methoden gibt es im C-Quelltext nicht, diese Unterscheidung ist dann die Aufgabe des Lx0-Compilers.

Pragmatisch

Ist eine sehr pragmatische Implementierung von OOP in C. Die Vorteile sind, dass der Quelltext leicht lesbar ist und im C-Programmcode viele Typüberprüfungen vorhanden sind, die beim Debuggen helfen. Der Nachteil ist, dass diese Implementierung recht unflexible ist. Wenn eine Klasse erweitert wird müssen auch die Quelltexte alle davon abgeleiteten Klassen angepasst und neu compiliert werden. Ersteres macht aber ja der Lx0-Compiler und letzteres stört bei kleineren Programmen erst einmal nicht. Später kann dann ein flexibleres OOP-Model benutzt werden aber zu Anfang gefällt mir zum Debuggen dieses leicht lesbare besser.

Bevor ich nun nächste Woche den Lx0-Compiler um OOP erweitere werde ich aber als nächstes erst einmal an einen Stück meine JavaFX-Beispiele an JavaFX 1.2 anpassen.

Quelltext

Der komplette OOP in C-Quelltext steht in Subversion.

[Direktlink]

< Voriger Tag   Nächster Tag >

  RSS V0.91

<Juli 2009 >
  0102030405
06070809101112
13141516171819
20212223242526
2728293031  

Home-Produkte-Testarea-Kontakt-Datenschutz-Aktualisiert: 17-Jul-2009
(C) 2000-2018 by Sven Drieling