Globale Klassen in C++


Aktuelle versuche ich gerade ein Computerspiel zu programmieren. Dort gibt es das Problem, das einzelne Objekte Zugriff benötigen auf alle anderen Bereiche des Spiels und zusätzlich noch Multithreading verlangt wird. Diese Art des global-Zugriffs widerspricht ein wenig der objektorientierten Philosophie wonach eine Klasse nur immer die Variablen der eigenen Klasse sehen und verändern darf. Wie also kann man von dieser Regel ausnahmsweise abweichen? Mein erster Ansatz war die Verwendung von Pointern. Bekanntlich ist ein Pointer nur eine Adresse im Speicher und wer diesen verändert hat die Macht. Leider ergibt sich das Problem, dass Pointer die an Klassen via reference übergeben werden, zwingend im Constructor initialisiert werden müssen. Das geht jedoch nicht, weil die Klasse2 erst nach Klasse1 im Sourcecode vorhanden ist. Aber ich habe einen Ausweg gefunden der ein bizar anmutet, ohne Pointer auskommt und einen sehr mächtigen Ansatz bereitstellt zur Schaffung von globalen Klassen.

Die Idee lautet, zwei Ansätze miteinander zu kombinieren: das Erzeugen von globalen Variablen mit der Forward-Deklaration von Klassen. Hier ist das Minimalbeispiel:

 
#include <iostream>

class Physics {
public:
  int temp=2;
  void show();
};
class Game {
public:
  Physics myphysics;
  int frame=10;
  void run();
};
Game mygame;

void Physics::show() {
  mygame.frame++;
  std::cout <<mygame.frame<<"\n";
}
void Game::run() {
  std::cout <<"run game\n";
  mygame.myphysics.show();
}

int main() 
{
  mygame.run();
  return 0;
}

Es funktioniert so, dass es eine globale Game-Klasse gibt. Und jede Subklasse kann über diesen Pfad Zugriff erhalten. Damit diese Game-Klasse sauber instanziert werden kann, benötigt man Forward-Deklaration. Alternativ kann man auch Header-Files anlegen. Im Beispiel ist zu sehen, wie eine Subklasse von Game trotzdem Zugriff erhält auf die höhergelegene Ebene. Damit wird das Problem gelöst wie eine Child Klasse Zugriff erhält auf die Parent Klasse ohne komplizierte Pointer-Artithmetik bemühen zu müssen. Selbstverständlich ist der Codesnippet aus Sicht von sauberer Programmierung höchst problematisch. Weil jede Funktion im Grunde alles darf.

Vielleicht einige Details dazu. Im Programm wird eine globale Variable mygame definiert. In dieser Variable ist das komplette Spiel enthalten: mygame.physcis enthält die Physicengine, mygame.gui enthält die Variablen und Attribute der GUI und mygame.physics.car[0] enthält ein einzelnes Objekt des Spiels. Zugegeben, eine sehr abenteuerliche Vorstellung. Wenn man jedoch der Meinung ist, dass eine globale Klasse welche nur den SFML Screen enthält Sinn macht (schließlich wollen alle Klasse etwas darauf zeichnen), dann ist der obige Codesnippet nur die logische Erweiterung. Man hat dann nicht nur globalen Zugriff auf das Hauptfenster sondern darf auch die Physik-Engine ansprechen.

Was wäre die Alternative? Die Alternative wäre, globale Klassen zu verteufeln, also bereits eine globale GUI Klasse als Hexenwerk zu bezeichnen und stattdessen jede Klasse einzeln von GUI erben lassen um etwas ausgeben zu können. Und wenn man schon dabei ist, könnte man auch gleich die Verwendung von Pointern verbieten weil damit unkontrolliert auf den RAM zugegriffen wird. Nun, wie wir alle wissen sind jedoch globale Variablen und Pointer anerkannte Werkzeuge in der Programmiersprache C++ und der wichtigste Grund warum die Leute C++ und keine andere Sprache nutzen. Und auch im vorliegenden Fall eines Computerspiels zu Trafficsimulation ist die Verwendung gerechtfertigt, weil es ja darum geht die gewünschte Funktionalität zu realisieren.

ERFAHRUNGEN
Eine erster Test unter Realbedingungen verlief positiv. Der Zugriff auf die globale mygame Variable gelang tatsächlich aus unterschiedlichen Klassen. Ein wenig anders fühlt sich die Programmierung damit an, am ehesten kann man es mit der Löve Game Engine vergleichen. Das heißt, man hat nicht mehr den Eindruck in C++ sondern in Lua zu programmieren. Weil man eben nicht länger darüber nachgrübelt wie man Zugriff auf Variablen enthält sondern einfach nur den Pfad hinschreibt „mygame.physics.car.undsoweiter“ und dann eben weiß was da drinsteht. Wenn man das Programm startet läuft alles sauber durch, es gibt keine Speicherfehler oder ähnliches. Das einzige was man beachten muss ist zwingend Forward-Deklarationen (also Headerfiles) zu verwenden, sonst funktioniert die Initalisierung der Globalen Variable nicht korrekt. Schauen wir uns die Kernklasse etwas genauer an:

 
class Game {
public:
  Settings mysettings;
  GUI mygui;
  Physics myphysics;
};
Game mygame; /// global variable

Man hat ein Standard-Spiel mit den wichtigen Elementen und instanziert das in die bereits erwähnte mygame Variable. Mygame kann man sich wie eine Art von Pointer vorstellen, also ein Zeiger der auf den Anfang des Spiels verweist von wo aus man jeden Teilbereich auslesen kann. Egal in welcher Unterklasse gerade Sourcecode ausgeführt wird, er hat automatisch Zugriff auf diesen Pointer, gewissermaßen über Klassengrenzen hinweg. Theoretisch kann man über den absoluten Pfad sogar auf seine eigenen Varaiblenzugreifen. Das wichtigste aber ist, dass eine Sub-Klasse jetzt Zugriff erhält auf Klassen die darüber liegen (parent). Vergleichbar als wenn man in Unix „../“ eingibt, genauer gesagt ist mygame die Root-Wurzel „/“ von der aus man dann in die Programmbereiche absteigt „/myphysics.car“ beispielsweise.

Es gibt noch einen interessanten Nebeneffekt und zwar kann man auf Vererbung vollständig verzichten. Stattdessen ist Komposition das einzige Designpattern. Neue Klasse instanziert man in anderen Klassen und kann trotzdem später darauf zugreifen.

UPDATE
Leider hat sich das kleine Experiment nicht als zielführend erwiesen. Man hat zwar auf alles Zugriff, was aber dazu führt, dass bereits bei einem kleinen Miniprogramm mit 500 Zeilen Code in der Gliederungsansicht des Editors unendlich viele Variablen und Methoden angezeigt werden, die man prinzipiell alle auch aufrufen darf. Technisch geht es, der Compiler findet keinen Fehler, doch die Übersicht leidet darunter massiv. Es ist ungefähr so, als wenn man auf Klassen verzichtet und einfach die Funktionen alphabetisch untereinanderschreibt in einer großen Mega-Klasse. Ich will damit sagen, dass globale Variablen keine gute Idee sind, und man lieber nach Alternativen suchen sollte. Notfalls mit Parameterübergaben und ähnlichem arbeiten, aber der Scope einer Klasse sollte immer minimiert werden.

Advertisements

Ein Gedanke zu “Globale Klassen in C++

  1. Damit bei Visual C++ die Gliederungsansicht nicht zugemüllt wird, habe ich globale Variablen und Funktionen mittels „namespace tools { … } using namespace tools;“ eingeklammert. Man bekommt dann einen zusätzlichen Ast namens „tools“ in der Baumansicht angezeigt, der ist normalerweise eingeklappt und stört nicht. Und wegen des „using“ kann man das so nutzen wie bisher, man muss also nicht überall „tools::“ davor schreiben.

    Es gab trotzdem einige kleinere Probleme, das waren aber Fehler in Visual C++.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s