- C-Plusplus/CLI
-
C++/CLI ist eine von Microsoft entwickelte Variante der Programmiersprache C++, die den Zugriff auf die virtuelle Laufzeitumgebung der .NET-Plattform mit Hilfe von speziell darauf zugeschnittenen Spracherweiterungen ermöglicht.
C++/CLI erfüllt die ebenfalls von Microsoft entwickelte Spezifikation namens Common Language Infrastructure (CLI) zur Sprach- und Plattform-neutralen Entwicklung und Ausführung von .NET-Anwendungen. Programme, die in C++/CLI geschrieben sind, können vom Compiler in CIL-Zwischencode übersetzt und auf der virtuellen Maschine der .NET-Plattform betrieben werden.
Seit Dezember 2005 liegt ein offiziell von der ECMA ratifizierter Standard für C++/CLI vor.
Zurzeit ist Visual Studio ab Version 2005 der einzige verfügbare Compiler, der eine Implementierung von C++/CLI anbietet. Weitere Compiler befinden sich in der Entwicklung.
Inhaltsverzeichnis
Sinn und Zweck der Erweiterungen
Ziele bei der Entwicklung von C++/CLI waren:
- Schaffung einer eleganten Syntax, die gut zum bisherigen C++ passt. Ein Programmierer, der bereits mit C++ vertraut ist, soll die Spracherweiterungen als möglichst "natürlich" empfinden.
- Komfortable Unterstützung von Besonderheiten der CLI wie Eigenschaften, Ereignisse, generische Typen (Generics), automatische Speicherbereinigung (garbage collection), Referenz-Klassen, usw.
- Gute Unterstützung von Sprachmitteln, die im bisherigen C++ verbreitet sind, wie etwa Templates oder "deterministische Deinitialisierungen", und zwar für alle Typen, einschließlich der neuartigen CLI-Klassen.
- Kompatibilität mit bestehenden C++-Programmen durch das Einbringen von fast ausschließlich reinen Erweiterungen gegenüber ISO-C++.
Unterschiede zu Managed C++
C++/CLI ist das Ergebnis einer grundlegenden Überarbeitung von Managed C++, der ersten Version von C++ mit Spracherweiterungen für die .NET-Plattform. Managed C++ litt unter Akzeptanzproblemen, weil die Syntaxerweiterungen zum Teil schwer lesbar waren, als unnatürlich empfunden wurden, oder lieblos und behelfsmäßig in die Sprache integriert wirkten.
So wurden beispielsweise viele Schlüsselwörter eingeführt, die mit zwei Grundstrichen beginnen. Zwar ist dies bei Spracherweiterungen in C++ üblich, die große Anzahl solcher Schlüsselwörter, sowie deren starke Durchdringung in Programmen, die von den Erweiterungen Gebrauch machten, wirkten jedoch störend auf das Gesamtbild der Quelltexte.
Beispiele:
Managed C++ C++/CLI __gc __interface interface class Console::WriteLine(S"{0}", __box(15)); Console::WriteLine("{0}", 15); int f()__gc[]; // Deklaration array<int>^ f(); // Deklaration 1) Object* A __gc[] = { __box(41), __box(42) }; array<Object^>^ A = { 41, 42 }; 1) Deklaration einer Funktion f, die eine CLI-Reihung (array) zurückgibt.
Destruktoren und Finalisierer
Im Unterschied zu Managed C++ wird die Destruktor-Syntax ~T() nicht mehr auf den Finalisierer abgebildet. Destruktor und Finalisierer werden in C++/CLI unterschieden; der Finalisierer hat jetzt die Syntax !T(). Der Destruktor ist außerdem identisch mit der Funktion Dispose (dies wurde durch technische Änderungen an der CLR ermöglicht).
Weitere Neuerungen
Weitere Neuerungen gegenüber ISO-C++ sind: eine verbesserte Enumerierung (enum class), Delegaten, Verpacken (boxing), Schnittstellenklassen, versiegelte Klassen, Attribute, usw.
Objektzeiger
Die augenfälligste Neuerung ist die Syntax ^ für die Objektzeiger (manchmal auch Handles genannt). Beispiel:
T^ whole_object_pointer = gcnew T(a, b);
Dabei ist gcnew ein Operator zur Allozierung von Objekten, die von der automatischen Speicherbereinigung verwaltet werden.
Im Vergleich dazu die herkömmliche Syntax für Zeiger:
T* plain_old_pointer = new T(a, b);
Trennung von Deinitialisierung und Speicherfreigabe:
Anders als bei gewöhnlichen Zeigern wird beim Löschen von Objektzeigern (Handles) mittels delete zwar der Destruktor aufgerufen, nicht aber der Speicher freigegeben. Statt dessen wird der vom Objekt belegte Speicher durch die automatische Speicherbereinigung an das System zurückgegeben.
Im Unterschied zu anderen Sprachen mit automatischer Speicherbereinigung (z. B. C# oder Java) wird hier also die problematische Zusammenfassung der Verwaltung von Speicher und anderen Ressourcen voneinander getrennt: Speicher und andere Ressourcen werden nicht mehr zusammen mit Hilfe der Speicherbereinigung freigegeben ("deterministische Deinitialisierung"; siehe Finalisierung).
Als automatische Variablen anlegbare CLI-Objekte:
Eine weitere technische Neuerung und einer der wichtigsten Unterschiede zu anderen Sprachen mit automatischer Speicherbereinigung sind die als automatische Variablen (d.h. "auf dem Stack") anlegbaren CLI-Objekte. Die Lebensdauer von automatischen Variablen endet in dem Augenblick, in welchem sie ihren Gültigkeitsbereich verlassen.
Im Zusammenspiel mit den neuartigen Objektzeigern (Handles) bleiben in C++ dadurch häufig angewandte Programmiertechniken wie RAII (Abkürzung für engl. resource acquisition is initialization) auch für die mit der automatischen Speicherbereinigung verwalteten CLI-Objekte möglich. Fehleranfällige Kodier-Techniken, wie sie aus anderen Programmiersprachen bekannt sind, lassen sich damit vermeiden.
Dazu ein Beispiel in C++/CLI:
void Uebertragung() { MessageQueue source("server\\sourceQueue"); String^ mqname = safe_cast<String^>(source.Receive().Body); MessageQueue dest1("server" + mqname), dest2("backup" + mqname); Message^ message = source.Receive(); dest1.Send( message ); dest2.Send( message ); }
- Erläuterung:
- Beim Verlassen der Funktion Uebertragung (mit return oder beim Auftreten einer Ausnahme) rufen Objekte implizit ihre Funktion Dispose auf, und zwar in der umgekehrten Reihenfolge in der sie konstruiert wurden. Im obigen Beispiel also dest2, dest1 und dann source.
Wenn ein automatisches Objekt seinen Gültigkeitsbereich verlässt, oder beim Löschen mit delete, wird sein Destruktor aufgerufen. Der Compiler unterdrückt dann den Aufruf der normalerweise von der automatischen Speicherwaltung angestoßenen Finalisierungsfunktion.
Der Wegfall von Finalisierungsfunktionen kann sich insgesamt positiv auf die Ausführungsgeschwindigkeit auswirken, hilft aber noch andere Probleme zu vermeiden; zu Problemen bei Verwendung der Finalisierungsfunktion: siehe Finalisierung.
Im Unterschied zu C++/CLI muss beispielsweise in Java zur Ressourcenfreigabe eine Dispose-Funktion immer explizit aufgerufen werden. In C# gibt es so genannte Using-Blöcke, an deren Ende Deinitialisierungen vorgenommen werden. Die Using-Blöcke sind zwar eine Verbesserung gegenüber dem Dispose-Verfahren von Java, müssen aber immer mitangegeben werden, daher sind sie der deterministischen Deinitialisierung von C++/CLI ebenfalls unterlegen.
Vergleich mit anderen .NET-Sprachen
Mit C++/CLI erstellte Programme sind in der Regel etwas schneller als in anderen .NET-Sprachen geschriebene. Grund dafür ist der C++-Compiler, den es im Vergleich mit anderen Sprachen wie z. B. C# schon länger gibt, und in den schon beträchtlich mehr Aufwand zur Optimierung des erzeugten Codes gesteckt wurde. Zurzeit (2005) sind .NET-Programme, die mit C++/CLI erstellt werden etwa 20 bis 25 Prozent schneller als andere.
Eine Besonderheit von C++/CLI ist die Mischbarkeit von Code, der auf der virtuellen Maschine läuft, und Code, der direkt auf der CPU ausgeführt wird. Beide Arten von Programmcode können in einer einzigen Programmdatei zusammengestellt werden. Mit dieser Möglichkeit nimmt C++/CLI bislang eine Sonderstellung unter den .NET-Sprachen ein.
Weblinks
Wikimedia Foundation.