C++/Tutorial: 20. Weitere Sprachmittel

Aus Scientia
Version vom 6. April 2011, 07:34 Uhr von Alexis Hiemis (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Nach den vergangenen 19 Kapiteln solltet ihr C++ bereits ziemlich gut kennen, das letzte Kapitel beschreibt daher einige Dinge oberflächlich, die ihr vermutlich so schnell nicht brauchen werdet und kann daher auch problemlos ausgelassen werden. Das Kapitel zielt mehr darauf, dass ihr auch diese Sprachmittel erkennt und verstehen könnt, wenn ihr sie in fremdem Code lest und weniger darauf sich auf konkrete Probleme zu beziehen und sinnvolle Anwendungsbeispiele zu geben.

typeid

C++ bietet RTTI, das heißt Runtime Type Information und bedeuted, dass Objekte ihren Typ zur Laufzeit kennen.(Das ist normalerweise beim Compiler ausschaltbar und wird neben dem im folgendem besprochenem unter anderem für dynamic_cast benötigt). Diese Information kann mittels des typeid Operators abgerufen werden. Dieser gibt ein type_info Objekt zurück(das nicht kopierbar ist) und benötigt deswegen den Header <typeinfo>. Das type_info Objekt kann nicht viel, bietet aber die Operatoren == und != um Typen zu vergleichen. Verwendung findet dies z.B. in boost.any.

typeid(i) == typeid(int); //ist i ein int?

Außerdem bietet type_info eine Funktion name(), die einen string zurück gibt, der den Typ auf irgendeine Weise benennt. Ein Beispiel:

cout << typeid(i).name() << endl; //gibt beim GCC i aus

inline

ein wichtiges Mittel zur Optimierung von Programmen ist das inlining. Kleine Funktionen, wie z.B. getter und setter können nur durch den Methodenaufruf stark an der Performance ziehen, weswegen es hilfreich ist wenn eine Funktion geinlined wird. Inlining bedeuted, dass eine Funktion im Kompilat nicht mehr als Funktion existiert, sondern der Compiler den Code der Funktion quasi an die Stelle des Funktionsaufrufes setzt.
Klingt praktisch, wie also sagen wir dem Compiler, dass er eine Funktion inlinen soll? Die Antwort lautet: gar nicht. Inlining ist etwas was ganz im Ermessen des Compilers liegt, wenn der Compiler es für sinnvoll hält inlined er eine Funktion, sonst gar nicht. Dennoch gibt es einige Dinge, die zu beachten sind.
Damit der Compiler eine Funktion inlinen kann ist es logisch, dass er die Definition der Funktion kennt und nicht nur wie sonst die Deklaration, das heißt eine Funktion, die geinlined werden soll muss im Header implementiert werden, eine Möglichkeit wäre die Funktion direkt zu definieren:

//Im Header:
int add(int a, int b)
  {
  return a + b;
  }

Gleiches gilt für Memberfunktionen. Es kann aber unter Umständen unübersichtlich und auf jeden Fall unschön werden, wenn zwischen all den reinen Deklarationen, die in einem Header normalerweise stehen nun auch eine Implementierung zu finden ist, interessant wäre es doch die Funktion zwar im Header zu definieren, aber wie bei Templates auch an anderer Stelle zu implementieren. z.B. so:

//Im Header:
int add(int a, int b);
//später:
int add(int a, int b)
  {
  return a + b;
  }

Wenn wir das aber so tun beschwert sich der Linker, denn sobald wir den Header 2 mal inkludiert haben wurde auch die Funktion add 2 mal definiert, was normalerweise nicht erlaubt ist. C++ bietet ein Schlüsselwort um dem Linker zu sagen, dass er mehrere Definitionen zu erwarten hat: inline. Dieses Schlüsselwort funktioniert für Memberfunktionen sowie für freie Funktionen und sieht in der Anwendung an unserem Beispiel so aus:

//Im Header:
int add(int a, int b);
//später:
inline int add(int a, int b)
  {
  return a + b;
  }

Inline Assembler

C++ Code wird in Assemblersprache übersetzt, welche eine direkte Darstellung von Maschienencode ist. Es kann allerdings vorkommen, dass wir uns nicht damit begnügen können und direkt Assemblercode schreiben müssen, C++ bietet uns daher die Möglichkeit Assemblycode direkt in den C++code einzubetten und dazu dient die asm Anweisung, die jeder standardkonforme Compiler hat. Über die Form dieser Anweisung oder die Syntax des Assemblercodes sagt der C++ Standard nichts aus, sodass die Compilerunabhängigkeit des Sourcecodes in dem Fall verloren geht, für genauere Details muss man sich daher auch Compilerspezifisch informieren.

volatile

Manchmal kann es vorkommen, dass Variablen von außerhalb modifiziert werden müssen, in dem Fall ist es notwendig gewisse Optimierungen für die Variable auszuschalten, sodass sie aktuell im Speicher liegt und vom Compiler aus Performancegründen über eine Zeitspanne nicht einfach in einem CPU Register gehalten wird. Genau wie const, können Memberfunktionen mit volatile specifiern ausgestattet werden. Die Funktionsweise ist ebenso ähnlich, nur Funktionen, die mit volatile markiert sind können auf volatile-Objekte aufgerufen werden.

int volatile x;

new

Placement new

Bei der dynamischen Konstruktion von z.B. Objekten mit new fordern wir normalerweise Speicher an, in dem das Objekt liegen soll, aber das muss nicht sein. Gehen wir davon aus, dass wir bereits einen Pointer auf einen Speicherbereich haben und dort nun dynamisch ein Objekt anlegen wollen. Nun wir wollen also ein Objekt in diesem Bereich konstruieren ohne dabei Speicher anzufordern, dazu bietet C++ das sogenannte placement new mit der Syntax new(Speicherbereich) Typ(normale_Argumente). Nehmen wir einmal an, wir haben eine Klasse Bar(die kleiner als 1000 Byte ist) und wollen diese in einem 1000 Byte großem Speicherbereich instanzieren, der als char array angelegt wurde, das könnte dann wie folgt aussehen:

char memory[1000];
Bar* some_object(new(memory) Bar());

Nun haben wir einen Pointer auf ein Bar-Objekt, welches in memory liegt und können es ganz normal verwenden, nur wie werden wir es wieder los? Den Speicher müssen wir natürlich nicht mehr freigeben, dieser liegt ja in unserem memory Array, welches automatisch freigegeben wird und delete some_object würde auch zu einem Fehler führen, aber dennoch müssen wir das Objekt wieder zerstören, damit es selbst seine Resourcen wieder freigeben kann und dabei kommt es uns zu gute, dass wir in C++ Destruktoren auch wie Methoden aufrufen können. Anstatt delete some_object müssen wir also schreiben:

some_object->~Bar();

nothrow

Neben dem Placment new bietet der Standard noch ein weiteres new und zwar das nothrow new. Wenn wir new normal verwenden kann es passieren, dass das OS nicht in der Lage ist den Speicher zu allokieren, in dem Fall schmeißt new eine bad_alloc Exception. Nun kann es aber passieren, dass wir gar keine Exception wollen, die Alternative wäre new nothrow vom Typ nothrow_t aus dem Header <new> zu übergeben und das machen wir so: new(std::nothrow) typ(...);. In dem Fall gibt new NULL zurück, wenn der Speicher nicht allokiert werden kann.