Eine Kurze Geschichte der OOP - INLINE, C++ und Klassen

Previous topic - Next topic

Quentin

Der Vergleich mit Stephen Hawkings Büchlein über die Zeit ist natürlich stark übertrieben, aber in meiner grenzenlosen Bescheidenheit ist mir nichts passenderes eingefallen ;).

Nachdem Gernot diversen Anfragen zu objektorientierten Erweiterungen in GLBasic mehr oder minder ein Abfuhr erteilt hat und er meinte, wer es unbedingt haben wolle, solle sich mit INLINE etwas basteln, habe ich mir diesen Rat zu Herzen genommen und probiert, genau das in GLBasic umzusetzen. Eines gleich vorweg: Ich bin alles andere als fit in Sachen C++ und Objektorientierung. Wer also in den kommenden Ausführungen grobe Verstöße gegen C++ - oder OOP-Dogmen erkennt, darf mir das schonend beibringen.

Im Prinzip arbeitet GLBasic schon mit Klassen und Objekten. Wenn man z.B. folgendens Coding kompiliert,


TYPE t_irgendwas
   x
   y
ENDTYPE

LOCAL etwas AS t_irgendwas


wird daraus dieses C++ - Coding generiert:
(zu finden unter C:\Dokumente und Einstellungen\<Rechnername>\Lokale Einstellungen\Temp\glbasic\gpc_temp_class.h, <Rechnername> bitte durch eigenen Rechnernamen ersetzen)


class t_irgendwas
{
public:
   DGInt x;
   DGInt y;
   t_irgendwas()
   {
      x = 0;
      y = 0;
   }
   t_irgendwas(const t_irgendwas& _in_)
   {*this = _in_;}
   t_irgendwas& operator=(const t_irgendwas& _in_)
   {
      this->x = _in_.x;
      this->y = _in_.y;
   return *this;
   }

};


Aus unserem TYPE ist also eine Klasse geworden mit den beiden Attributen x und y, zwei Konstruktoren sowie einem überladenen = - Operator. Der = - Operator muss deshalb überladen werden, damit Zuweisungen wie


LOCAL etwas AS t_irgendwas
LOCAL nochwas AS t_irgendwas

etwas.x = 5
etwas.y = 7
nochwas = etwas


möglich sind. Die Instanzierung der Klasse erfolgt im Hauptprogramm gpc_temp0.cpp innerhalb der Funktion __MainGameSub__. Der Befehl lautet "REGISTER_VAR(t_irgendwas, etwas);"
Alle Attribute der Klasse sind prinzipiell als "public" definiert. Und genau das kann mit der Formulierung von eigenen Klassen mittels INLINE anders gestaltet werden.


Somit haben wir auch schon prinzipiell die Vorlage für die Implementierung von eigenen Klassen. Folgendes kleine Beispiel erstellt eine Klasse mit einigen Methoden, spielt ein wenig damit rum.

Code (glbasic) Select

INLINE
// Klassendefinition
class cirgendwas
{
private:
DGInt x;
DGInt y;

public:
// Standardkonstruktor
cirgendwas()
{
x = 0;
y = 0;
}

DGInt getx() { return x; }
DGInt gety() { return y; }
void setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}

};
ENDINLINE

INLINE
// ein Objekt anlegen
cirgendwas etwas;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

WHILE TRUE
INLINE
//x und y zufällig setzen
etwas.setpos(RND(scrx), RND(scry));
x = etwas.getx();
y = etwas.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
SHOWSCREEN();
SLEEP(1500);
ENDINLINE
WEND


Läuft schon mal, wenn es auch wenig sinnvoll ist.
Im Beispiel sind die Funktionen innerhalb der Klassendefinitionen ausformuliert. Wenn man das so macht, sind diese Klassenmethoden implizit als inline deklariert (soweit ich richtig informiert bin). In diesem Beispiel wäre das sicherlich kein Schaden. Wenn eine Methode jedoch etwas umfangreicher ist und an vielen Stellen im Coding aufgerufen wird, kann dies den Code aufblähen. Also probieren wir doch mal, unsere Klassendefinition so umzustellen, daß in der Klasse nur die Methoden definiert werden und das eigentliche Coding außerhalb ...

Code (glbasic) Select

INLINE
// Klassendefinition
class cirgendwas
{
private:
DGInt x;
DGInt y;

public:
cirgendwas();
DGInt getx();
DGInt gety();
void setpos(const DGInt new_x, const DGInt new_y);
};

cirgendwas::cirgendwas()
{
x = 0;
y = 0;
}

DGInt cirgendwas::getx() { return x; }
DGInt cirgendwas::gety() { return y; }
void cirgendwas::setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}
ENDINLINE


... und erleben eine böse Überraschung. Der Compiler überhäuft uns mit freundlichen, auf den ersten Blick unverständlichen Fehlermeldungen. Auf den zweiten Blick (in meinem Fall kam der zweite Blick von Gernot) wird dann auch klar, warum die Fehlermeldungen kommen. Die Stelle, an der ich die Klassendefinition vorgenommen habe, liegen im von GLBasic erzeugten C++ - Programm innerhalb der Main-Funktion, genauer innerhalb der Funkton __MainGameSub__, die von der WinMain-Funktion aufgerufen wird. Dadurch, daß ich die Methoden außerhalb der Klassendefinition gesetzt habe, werden diese innerhalb einer Funktion __MainGameSub__ geschrieben. Funktionsdefinitionen innerhalb einer Funktion sieht kaum ein Compiler gern.

Folglich muss ich eine Möglichkeit finden, die Klassendefinition global bekannt zu machen. Wie man das im Programm darstellt, wurde ja schon an vielen Stellen im Forum beschrieben.


// leere Funktion, damit das folgende Coding außerhalb der Main-Funktion liegt
FUNCTION __dummy:
ENDFUNCTION

INLINE
// ab hier kann dann die Klassendefintion + Implementierung liegen
ENDINLINE


Jetzt kommt das nächste Problem. Die Klasse liegt zwar jetzt außerhalb einer Funktion, in der Main-Funktion ist sie jedoch nicht bekannt, weil sie im generierten Coding NACH der Main-Funktion liegt. Man kann sich damit behelfen, daß man sich seine eigene "Main-Funktion" bastelt, die nach dem INLINE-Block liegt. Schematisch ist das Programm dann folgendermaßen aufgebaut.


// Aufruf der eigenen Main-Funktion
MyMain()

// Main beenden mit Dummy-Funktion
FUNCTION __dummy:
ENDFUNCTION

// Klassendefinition
INLINE
class ...
ENDINLINE

// eigene Main-Funktion
FUNCTION MyMain:
...
ENDFUNCTION



Hier noch mal das komplette Coding:

Code (glbasic) Select

// Aufruf der eigenen Main-Funktion
MyMain()


FUNCTION __dummy:
ENDFUNCTION

INLINE
// Klassendefinition
class cirgendwas
{
private:
DGInt x;
DGInt y;

public:
cirgendwas();
DGInt getx();
DGInt gety();
void setpos(const DGInt new_x, const DGInt new_y);
};

// Implementierung der Klassenmethoden
cirgendwas::cirgendwas()
{
x = 0;
y = 0;
}

DGInt cirgendwas::getx() { return x; }
DGInt cirgendwas::gety() { return y; }
void cirgendwas::setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}
ENDINLINE

// ------------------------------------------------------------- //
// ---  MYMAIN  ---
// ------------------------------------------------------------- //
FUNCTION MyMain:

INLINE
// ein Objekt anlegen
cirgendwas etwas;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

WHILE TRUE
INLINE
//x und y zufällig setzen
etwas.setpos(RND(scrx), RND(scry));
x = etwas.getx();
y = etwas.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
SHOWSCREEN();
SLEEP(1500);
ENDINLINE
WEND

ENDFUNCTION // MYMAIN



Fein, jetzt haben wir das Rüstzeug für weitere Untaten. Und zwar Array's. Häufig will man ja nicht nur eine Variable eines TYPE's (bzw. eines Objektes) haben, sondern gleich ein ganzes Array davon. Da wir uns jetzt sowieso schon in C++ bewegen, stehen einem diverse Möglichkeiten zur Verfügung


  • man ertellt sich C++ - Arrays (cirgendwas etwas[20]; )
  • man nutzt verkette Listen
  • man schreibt sich eine eigene Array-Klasse
  • oder ... man ist ganz clever und nutzt die Funktionalität von GLBasic

Wir erweitern obiges Beispiel, indem wir ein Array der Klasse cirgendwas anlegen. Dies kann "ganz einfach" mit der Anweisung


DGArray<cirgendwas> arr_etwas;


geschehen. Wie in der GLBasic-Hilfe beschrieben, ist DGArray eine Template-Klasse. Diesem müssen wir natürlich mitteilen, von welchem Typ wir eine Klasse wünschen (<cirgendwas>). Auf dieses Array kann man jetzt alle bekannten GLBasic-Befehle wie DIM, REDIM, DIMPUSH, DIMDEL anwenden.

Hier nur die geänderte MyMain-Funktion:

Code (glbasic) Select

// ------------------------------------------------------------- //
// ---  MYMAIN  ---
// ------------------------------------------------------------- //
FUNCTION MyMain:

LOCAL i

INLINE
// ein Objekt anlegen
cirgendwas etwas;
DGArray<cirgendwas> arr_etwas;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

// einfach mal 10 Elemente erzeugen
FOR i = 0 TO 9
INLINE
etwas.setpos(RND(scrx), RND(scry));
DIMPUSH(arr_etwas, etwas);
ENDINLINE
NEXT

WHILE TRUE
INLINE
// Array ausgeben mit der "FOREACH-Schleife"
for(i = 0; i <= LEN(arr_etwas()) - 1; )
{
cirgendwas& a = arr_etwas(i++);
x = a.getx();
y = a.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
}
SHOWSCREEN();
ENDINLINE
WEND

ENDFUNCTION // MYMAIN


Noch ein Wort zur "FOREACH-Schleife" innerhalb von WHILE-WEND. Dieses Konstrukt habe ich abgekupfert, indem ich nachgesehen habe, wie GLBasic eine FOREACH-Schleife in C++ - Code übersetzt. Es fällt auf, daß der Schleifenzähler "i" nicht in der FOR-Anweisung hochgezäht wird, sondern erst innerhalb der Schleife. Das ist wichtig, da sonst die Schleife mit dem zweiten Element beginnen würde und beim letzten Durchlauf auf einen Fehler läuft, da man auf ein nicht existierendes Element des Arrays zugreifen wollte.

Die Anweisung


cirgendwas& a = arr_etwas(i++);


liefert in der lokalen Variablen a die Adresse auf das aktuelle Array-Element. Mit diesem können wir dann wieder ganz normal mit den Klassen-Methoden arbeiten. Das Löschen von Array-Elementen mit DELETE wird übersetzt in die Anweisungen


DIMDEL(arr_etwas, --i);
continue;


In der GLBasic-Hilfe zu INLINE ist beschrieben, daß bei der Verwendung von DGArray die Operatoren "=" und "()" definiert werden müssen. Das obige Beispiel oder auch das von Schranzor (http://www.glbasic.com/forum/index.php?topic=2224.0) sind aber auch ohne überladene Opertoren lauffähig. In welchen Fällen das dann wirklich erforderlich ist, entzieht sich leider meiner Kenntnis.

Jetzt erst einmal genug davon.

Fazit:
Das Rumgefummel mit Klassen und INLINE macht richtig Spaß. Was die Sinnhaftigkeit angeht, bin ich jedoch arg im Zweifel. Was erreicht man durch die ganze Plackerei, die einem die ungezählten Fall-Stricke der C++ - Programmierung in den Weg legen. Datenkapselung. Schön und gut. Ob das jedoch den Aufwand rechtfertigt, kann ich nicht abschließend beantworten. Zur Zeit macht es einfach Spaß, damit rumzuspielen.

P.S.
Wenn Schranzor mich jetzt dazu verdonnert, diesen ganzen Wirsing ins englische zu übersetzen, ist mein Urlaub ganz versaut ;)



Schranz0r

 :good:

Aber die Codes darfst du in "Insert Code"-Boxen setzen :)
Sonst echt top ;)
I <3 DGArray's :D

PC:
AMD Ryzen 7 3800X 16@4.5GHz, 16GB Corsair Vengeance LPX DDR4-3200 RAM, ASUS Dual GeForce RTX™ 3060 OC Edition 12GB GDDR6, Windows 11 Pro 64Bit, MSi Tomahawk B350 Mainboard

Quentin

ja Meister, dein Wunsch ist mir Befehl :giveup:

obwohl ich finde, daß
Code (glbasic) Select
[tt]...[/tt] schöner formatiert als Code, z.B. werden Tabs dort nur in 4 Leerzeichen umgewandelt, bei Code in 8. Könnte man das ändern? Das sieht bei vielen Beispielcodings hier im Forum manchmal recht unschön aus.

Kitty Hello

Quote
In der GLBasic-Hilfe zu INLINE ist beschrieben, daß bei der Verwendung von DGArray die Operatoren "=" und "()" definiert werden müssen. Das obige Beispiel oder auch das von Schranzor (http://www.glbasic.com/forum/index.php?topic=2224.0) sind aber auch ohne überladene Opertoren lauffähig. In welchen Fällen das dann wirklich erforderlich ist, entzieht sich leider meiner Kenntnis.
Die Klasse DGArray<> definiert die operatoren = und ().
GLBasic:
a[5] = b[7][8]
C++
a(5) = b(7,8);

GLBasic:
a[] = b[]
C++
a=b;

Sonst super geniales Tutorial. Vielen Dank.
Bitte noch auf engl. übersetzen ;)


Quentin

ok, danke, dann ist das klar. Ich war mir nicht sicher, ob die Operatoren in bestimmten Fällen nochmals redefiniert werden müssen.

Übersetzung ist im englischen Forum drin   =D