C++/CLI

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche

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.

Microsoft Visual Studio ab Version 2005 und das Compiler-Frontend der Edison Design Group bieten eine Implementierung von C++/CLI an.[1]

Sinn und Zweck der Erweiterungen[Bearbeiten]

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++[Bearbeiten]

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 viele Programmierer die Syntaxerweiterungen als schwer lesbar empfanden. Beispielsweise enthielt die Syntax Bestandteile, die nicht eindeutig erkennen ließen, ob verwaltete oder nichtverwaltete Objekte erzeugt werden.[2]

Auch wirkten die mit Managed C++ eingeführten Syntaxelemente für viele Programmierer behelfsmäßig in die Sprache integriert. So waren z.B. viele Schlüsselwörter eingeführt worden, die mit zwei Grundstrichen beginnen. Zwar ist dies bei Spracherweiterungen gegenüber Standard-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[Bearbeiten]

Weitere Neuerungen gegenüber ISO-C++ sind: eine verbesserte Enumerierung (enum class), Delegaten, Verpacken (boxing), Schnittstellenklassen, versiegelte Klassen, Attribute, usw.

Objektzeiger[Bearbeiten]

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. Stattdessen 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[Bearbeiten]

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.

Einzelnachweise[Bearbeiten]

  1. http://www.edg.com/docs/edg_cpp.pdf C++-Frontend der Edison Design Group (EDG)
  2. C++: The Most Powerful Language for .NET Framework. Abgerufen am 21. Oktober 2010.

Weblinks[Bearbeiten]