Hallo,
da viele hier im Forum irgendwann anfangen C++ zu lernen, aber ein Großteil der Bücher und Tutorials veraltet oder falsch ist, erkläre ich hier mal die
Grundprinzipien für sauberes C++.
-
Objektorientierte Programmierung (OOP):
OOP ist das grundlegende Feature von C++ und heißt, dass Daten in Objekten gekapselt sind.
Eine Klasse ist der Datentyp eines Objekts.
Das ist ähnlich wie ein DllStruct, nur kann man neben ein paar anderen Features zusätzlich Methoden definieren, das sind Funktionen, die mit dem Objekt
verbunden sind. Wenn man daran gewöhnt ist, ist $variable.length() einfacher zu lesen, als StringLength($variable), außerdem kann man die internen Daten
eines Objekts verstecken, sodass man sie von außen nicht ändern kann, Methoden haben aber immernoch volle Zugriffsrechte darauf.
Man kann Operatoren (+, &, +=, etc) für eigene Objekte selber definieren und so übersichtlich mit ihnen programmieren, indem man z.B. eine Vektorklasse hat und die normalen
mathematischen Schreibweisen zulässt.
-
Vererbung:
Vererbung heißt, man leitet eine Klasse von einer anderen ab. Die abgeleitete übernimmt alle Eigenschaften der Basisklasse, kann aber Methoden überschreiben
und zusätzliche definieren. Dadurch kann man eine allgemeine Basis haben und die immer weiter spezialisieren. Zum Beispiel kann die Basisklasse Tier nehmen
und Verhaltensweisen wie Essen und schlafen definieren. Dann leitet man die spezialisierte Klasse Hund ab und fügt Bellen hinzu und leitet als zweites Vogel ab
und fügt Fliegen hinzu, überschreibt aber das normale Essen mit Körnerpicken.
-
Konstruktor:
Der Konstruktor ist einfach eine Funktion, die ein Objekt initialisiert.
[autoit]Func createVector($x, $y)
[/autoit]
Local $struct = DllStructCreate("int x; int y;")
DllStructSetData($struct, "x", $x)
DllStructSetData($struct, "y", $y)
return $struct
EndFunc
wäre quasi ein Konstruktor, aber richtige Konstruktoren sind übersichtlicher und sicherer, weil sie implizit aufgerufen werden:
Beiwird automatisch ein Konstruktor aufgerufen, der den String "Hello, world!" in das Objekt kopiert.
Würde man altes C verwenden, müsste man erst selbst den Speicher reservieren mit
und dann füllen mit -
Destruktor:
Er ist quasi das Gegenteil des Konstruktors. Wenn das Objekt gelöscht wird, sorgt er dafür, dass alles ausgeräumt wird. Bei einem string sorgt er zum Beispiel dafür, dass der Speicherplatz wieder freigegenben wird. Bei dem anderen Beispiel von C muss man dafür selber sorgen mit
und wenn man das irgendwo vergisst, hat man Memory leaks.
Mehr dazu schreibe ich später. -
Exceptions:
Exceptions sind Ausnahmen, die irgendwo bei einem Fehler ausgelöst werden. Sie übernehmen die Aufgabe von @error, aber @error muss nach jedem Schritt überprüft werden, gegenenenfalls die Funktion beendet und @error neu gesetzt werden muss
[autoit]Func Foo()
Bar()
if @error Then Return SetError(@error)
; blablabla
EndFunc[/autoit]
und das wird von einer Funktion in die nächste weitergegeben, bis es eine ignoriert oder verarbeitet.
Dagegen beenden Exceptions automatisch alle anderen Funktionen und springen zu einer Stelle, die vorher mit try und catch definiert wurde
Codetry { // ganz gang viele, komplizierte Funktionen } catch (exception e) { cout<<e.what(); // das heißt, die Feherbeschreibung wird ausgegeben }
Der Vorteil ist, dass ein Fehler an ganz anderer Stelle bearbeitet werden kann, wenn z.B. beim Einlesen von Dateien ein Fehler auftritt, der Fehler vom Manager aufgefangen und an GUI oder Konsole weitergegeben werden. Dadurch erreicht man eine striktere Trennung von verschiedenen Bereichen und mehr Übersichtlichkeit. -
Namespaces:
Namespaces sind wirklich kein großartiges Feature.
Es erlaubt nur Namensräume, quasi Codeabschnitte mit Namen zu definieren und so zu verhindern, dass sich Namen überschneiden. Zum Beispiel gibt es in der Standard-library (= standard-UDFs) die Klasse string, man hat vielleicht aber auch eine andere Klasse, die genauso heißt. Deswegen schreibt man std::string und bestimmt dadurch, das man die Klasse/Funktion aus std (Standard library) haben will. Bei AutoIt macht man das ähnlich, indem man _WinAPI_... schreibt, aber die namespaces sind da noch etwas mächtiger und übersichtlicher. Wenn man selber eine library schreibt, sollte man namespaces nutzen und wenn man eine nutzt, sollte man sich sehr gut überlegen, ob man den Namespace mittels
in den globalen integriert. Leider ist es in Tutorials sehr verbreitet, denn es erspart Tipparbeit, wenn man nur string statt std::string schreibt, aber umso größer und komplexer das Pojekt, umso wahrscheinlicher wird es auch, dass sich Namen überschneiden, Fehler und Probleme bei der Zuordnung auftreten.
Leider lernen die meisten Anfänger das falsch und gewöhnen sich schnell daran. -
Templates:
Templates sind eine Art zusätzlicher Parameter, der beim Compilen bestimmt wird. Im Gegensatz zu anderen Parameter können sie auch Typen enthalten. Sie sind sehr praktisch, wenn man eine allgemeine Funktion oder allgemeines Objekt für verschiedene Datentypen haben will. Eine template-classe ist beispielsweise list, eine verkettete Liste aus der Standard library Mit
erzeigt man eine Liste aus integern, mit
eine aus Strings und mit
eine für die selbst definierte Klasse. In C musste man noch Funktionen für alle drei Dateintypen selbst definieren und dadurch mehrmals fast den gleichen Code schreiben. Fast alles aus der Standard-library enthält templates, damit man es so allgemein wie möglich halten kann, deswegen spricht man auch von der standard template library, kurz STL.
Templates selber können noch viel mehr, als nur Typen dynamisch zu machen und für solche Zwecke zwar sehr einfach, aber wenn man den vollen Funktionsumfang nutzen will, sind eines der kompliziertesten Dinge aus C++. Sie haben quasi den Umfang einer kompletten Programmiersprache, die aber während des Compilens umgesetzt wird und so maximale Übersichtlichkeit und Sicherheit bei keinem Geschwindigkeitsverlust bieten. -
RAII:
Dieses ist wohl der wichtigste Punkt, denn dieses Konzept ist das grundlegende für sauberes C++, vermeidet fast alle Flüchtigkeitsfehler, die zu Memory Leaks, fehlerhafter Threadsteuerung oder anderen fehlern bei Resourcen führen. Es wurde früher schon eingeführt aber enthielt immer noch Lücken, in C++11 (der neue Standard von 2011) wird es aber konsequent und fast vollständig umgesetzt und mit C++14 noch weiter ausgebaut. Es steht für Resource Acquisition Is Initialization und bedeutet dass alle Daten in Objekte gekapselt werden, die bei der Objekterzeugung zugewiesen und bei Zerstörung der Objekte vom Destruktor automatisch freigegeben wird.
Zum Beispiel muss man in AutoIt Dateien mit FileOpen öffnen und mit FileClose wieder schließen. Dabei können Fehler auftreten, indem FileClose nicht aufgerufen wird.
[autoit]Func foo()
Local $file = FileOpen("Datei.txt")
If $file=-1 Then Return SetError(1) ; beende mit exit code 1
Local $string = FileRead($file, 5)
If StringLen($string)<>5 Then Return SetError(2) ; beende mit exit code 2; anderer code
FileClose($file)
[/autoit]
EndFunc
Hat die Datei weniger als 5 Buchstaben, wird frühzeitig mit Return abgebrochen, die Datei bleibt geöffnet und kann von anderen Programmen nicht mehr verwendet werden. Man kann die Datei aber auch nachträglich nicht mehr schließen, weil $file nur lokal ist und deswegen nach Ende der Funktion nicht mehr existiert. Man muss also an jedem möglichen frühzeitigen Ende alle Resourcen aufräumen.
Bei AutoIt ist es aber harmlos, weil nur durch Return, ExitLoop, ContinueLoop, if-Abfragen, Switch und Select ein FileClose oder ähnliche Funktion ausgelassen wird, wobei nur Return wirklich relevant ist, weil man meistens am Ende der Funktion aufräumt.
In C++ hingegen gibt es alle diese Fälle auch, aber zusätzlich auch noch goto, longjump (ähnlich wie goto, aber über Funktionsgrenzen hinweg), auf diese beiden sollte man aber meistens verzichten, und Exceptions, die an jeder Stelle des Codes ausgelöst werden können, auch innerhalb von aufgerufenen Funktionen und teilweise unabsehbar. Es kann auch passieren, dass beim Aufruf einer Funktion ein Parameter eine exception auslöst und so die Resource des andere Parameter nicht freigegeben werden kann. In C+ hat (und braucht) man auch deutlich direkteren Zugriff auf den Arbeitsspeicher und kann deswegen sehr leicht memory-leaks auslösen.
C++ garantiert aber, dass alle Objekte beim Verlassen des Scopes gelöscht und die Destruktoren aufgerufen werden. Deswegen kapselt man die Resourcen in ihnen und egal was passiert (Außnahme Absturz), es ist gesichert, dass alle Resourcen freigegeben werden.
Für Dateien gibt es fstream, das RAII und andere Vorteile in sich vereint und man sollte sie deswegen nutzen und nicht fopen und fclose. Will man einen container (array, liste, map, hashmap u.v.m.) haben, sollte man sich keine wie in C selbst basteln und mit Funktionen alles selbst verwalten und freigeben, sondern die fertigen nutzen, die neben RAII (auch die Destruktoren des Inhalts werden aufgerufen) auch generisch und damit übersichtlicher sind.
Wenn ein Objekt länger als der Scope leben soll, dann kann man es dynamisch mit der Funktion new erzeugen und einen Zeiger darauf übergeben, aber dann muss man es auch wieder selbst freigeben mit delete (delete ruft auch den Konstruktor auf), aber ein Zeiger ist nicht RAII, denn wenn er gelöscht wird, bleibt der Speicher erhalten. Deswegen sollte man hierfür smart-pointer nutzen, denn diese existieren als Objekt, das den Speicher im Destruktor unter der Voraussetzung freigibt, dass es nicht vorher kopiert wurde, dann wird nämlich der Zeiger übergeben und das andere Objekt lässt ihn leben.
Ich denke jetzt mal, dass ihr vielleicht nicht viel oder zumindest nicht alles verstanden habt, aber denkt daran, wenn ihr mal C++ lernt, euch hiermit und anderen Artikeln über modernes C++ auseinanderzusetzen, denn ein recht großer Teil der C++-Programmierer hat die Sprache nie wirklich verstanden oder verwendet noch Programmierweisen aus den 80ern, ohne zu beachten, dass sich C++ weiterentwickelt und besser wird. Leider ist ein Teil dieser Programmierer auch noch der Meinung Tutorials oder Bücher schreiben zu müssen und leider gibt es inzwischen sehr viele Bücher/Tutorials, die falsches beibringen.
Mfg Marthog