Von C# zu C++: Referenztypen

C# ist eine moderne, bequeme und einfache Sprache, die viele Schwierigkeiten vor uns verbirgt und uns das Leben in vielen Situationen sehr einfach macht. Manchmal werden dabei jedoch wichtige Konzepte vor uns verborgen die für ein solides Verständnis wichtig wären. Einer dieser Punkte sind die Referenzen und ich möchte in diesem kurzen Artikel ein paar Worte darüber verlieren, was ein Referenzdatentyp überhaupt ist und wie sich dieser sowohl in C#, als auch in C++ verhält.

Dies soll weder ein C# noch ein C++ Programmierkurs sein, sondern lediglich kurz die Gemeinsamkeiten und das Verhalten in beiden Sprachen beleuchten um es dem Entwickler zu erleichtern sich in beide Sprachen einzuarbeiten. Da ich in der nächsten Zeit häufiger etwas über C++ und DirectX schreiben werde sind solche Grundlagenartikel sicherlich hilfreich für das Verständnis und sollen dem geneigten Umsteiger das Leben etwas vereinfachen.

Verweis- bzw. Referenztypen in C# sind Datentypen, bei denen eine Referenz auf ein Objekt gespeichert wird. Dabei wird das eigentliche Objekt (meist eine Klasse) im Arbeitsspeicher abgelegt und die Referenzvariable enthält eine Referenz auf dieses Objekt.

Ich möchte dies nun an einem kleinen und einfachen Beispiel verdeutlichen. Dazu erzeugen wir zunächst eine neue Klasse in einem leeren C#-Projekt. Eine Konsolenanwendung eignet sich dafür ganz gut.

Wir erzeugen nun mit dem folgenden Code in einer neuen Klassendatei einen Referenzdatentyp MyReferenceDataType.

public class MyReferenceDataType
{
  public int SomeValue
  {
    get;
    set;
  }
}

Kein besonders komplizierter Code und auch Einsteiger sollten diesen Code verstehen, ist die Klasse doch eines der Grundkonzepte von C#. Etwas besonderes macht diese Klasse auch nicht. Sie kann lediglich einen Integer-Wert SomeValue speichern und zurückgeben.

Nun erzeugen wir eine Instanz dieses Wertes im Speicher. Bekanntlich erfolgt dies mit dem new Befehl.

MyReferenceDataType reference = new MyReferenceDataType();
reference.SomeValue = 42;
Console.WriteLine(reference.SomeValue);

Interessant ist jetzt was hinter den Kulissen passiert. reference ist eine sogenannte Referenzvariable und stellt nicht das eigentliche Objekt im Speicher dar, sondern lediglich eine Referenz auf diese Instanz. Im Grunde genommen (wenn wir mal die Garbage Collection etc. von C# ausser Acht lassen) enthält reference einen Zeiger auf die Instanz des Typs MyReferenceDataType das sich irgendwo im Arbeitsspeicher befindet. C# und das .NET-Framework verbergen das aber geschickt vor uns. Mit unsafe Code kann man zwar auch in C# mit Zeigern hantieren, aber das ist eine andere Geschichte.

Greifen wir nun auf SomeValue zu (wie in der zweiten Zeile dargestellt), so holt sich der Rechner also die Adresse die in reference gespeichert ist und greift auf die tatsächliche Instanz zu. Dies führt dazu, dass wir Referenzen schnell und effizient kopieren können, da nicht die eigentlichen Daten im Arbeitsspeicher dupliziert werden müssen, sondern lediglich der Zeiger.

MyReferenceDataType referenceCopy = reference;  // nicht das Objekt wird kopiert, sondern lediglich die Referenz darauf
referenceCopy.SomeValue = 4711;
Console.WriteLine(referenceCopy.SomeValue);
Console.WriteLine(reference.SomeValue);

Der Beweis dafür ist relativ einfach zu erbringen, wie ich im vorherigen Codeblock dargestellt habe. Zunächst kopieren wir die Referenzvariable reference in die neue Referenzvariable referenceCopy. Die „alte“ Referenzvariable enthält immer noch 42 in der Eigenschaft SomeValue. Nun weisen wir SomeValue in der Kopie der Referenz einen Wert von 4711 zu. Wenn beide Referenzvariablen also auf die gleiche Instanz von MyReferenceDataType im Arbeitsspeicher zeigen, so müssten danach beide Referenzen den Wert 4711 zurückliefern. Dies überprüfen wir mit den beiden Konsolenausgaben und sehen, dass wir tatsächlich eine Referenz auf ein und dieselbe Instanz vorliegen haben.

Dieses Verhalten sollte jedem C#-Entwickler bekannt sein, denn es ist ein sehr wichtiges Konzept.

In C++ kann man sowas natürlich ebenfalls abbilden und zwar mit Zeigern (also Pointer). Der Syntax ist dabei ähnlich. Wir erzeugen uns dazu zunächst ein leeres C++-Projekt in Visual Studio. Zunächst erzeugen wir wieder unsere Klasse. Dies erfolgt in der Datei MyReferenceDataType.h. Es reicht ein Header aus, da wir lediglich eine öffentliche Variable SomeValue deklariert haben.

#ifndef _MYREFERENCEDATATYPE_H_
#define _MYREFERENCEDATATYPE_H_

class MyReferenceDataType
{
public:
	int SomeValue;
};

#endif

Danach packen wir in die Main-Funktion unserer C++-Konsolenanwendung den „Testcode“.

	MyReferenceDataType* reference = new MyReferenceDataType();
	reference->SomeValue = 42;

	std::cout << reference->SomeValue << std::endl;

	MyReferenceDataType* referenceCopy = reference;
	referenceCopy->SomeValue = 4711;

	std::cout << reference->SomeValue << std::endl;
	std::cout << referenceCopy->SomeValue << std::endl;

	delete reference;

	getchar();

Damit dies funktioniert musste ich noch stdio und iostream mittels der Include-Direktive einbinden.

Wenn wir das Programm kompilieren und starten, dann sehen wir, dass es das gleiche Verhalten an den Tag legt wie auch unser C#-Programm. Auch hier speichern wir Referenzen, also Zeiger auf eine Instanz im Speicher und greifen hier mit dem Dereferenzierungs-Operator -> darauf zu.

Da C++ uns mehr Macht verleiht, müssen wir leider auch auf einige Dinge mehr achten. Instanzen werden nicht automatisch freigegeben, so wie wir es von C# gewöhnt sind, da es keinen Garbage-Collector gibt. Wir müssen uns darum selbst kümmern und das macht in diesem Fall die Zeile delete reference. Diese gibt den Speicher frei, den wir zuvor mit new reserviert hatten. Wir dürfen natürlich nur einmal delete verwenden, da wir nur eine Instanz haben. Die Variablen reference und referenceCopy sind lediglich Zeiger auf diesen Speicherbereich. Der Zeiger wird übrigens ungültig sobald delete ausgeführt wurde, daher gehört es in C++ zum guten Stil, wenn man den Zeiger danach durch Zuweisung mit 0 ungültig macht.

Abschluss und Schlusswort

Und schon sind wir am Ende dieses kurzen Artikels angelangt. Wie bereits in den einleitenden Worten geschrieben ist dies kein Programmierkurs und daher habe ich nicht alle Aspekte und Unterschiede von C# und C++ Referenzen und Zeigern aufgezeigt, sondern lediglich eine kurze Einführung gegeben. Für den C#-Entwickler konnte ich vielleicht ein wenig Licht ins Dunkel bringen und aufzeigen, was hinter den Kulissen so passiert. Für den Umsteiger von C# nach C++ habe ich den Umstieg vielleicht ein klein wenig erleichtert und der C++-Entwickler, der sich mit C# beschäftigen möchte weis nun, wie Zeiger in C# abgebildet werden.

Ich hoffe dieser Artikel hat gefallen. Über ein kurzes Feedback würde ich mich wie immer natürlich sehr freuen. Es wäre interessant zu erfahren ob Artikel in dieser Art zukünftig weiterhin gewünscht werden. Themenvorschläge sind natürlich auch immer willkommen.

Advertisements

Veröffentlicht am 07.02.2012 in C#, C/C++, Grundlagen und mit , , , , , getaggt. Setze ein Lesezeichen auf den Permalink. 9 Kommentare.

  1. NeoArmageddon

    Auch wenn ich den Zusammenhang bereits kannte, ich das mal wieder ein schöner Artikel.

    Ich werde den Link mal bei Gelegenheit den Leuten in meinem C++ Kurs geben. Anfänger scheinen nämlich immer wieder über die Pointer und deren Zusammenhänge zu stolpern. Vielleicht gefällt dem ein oder anderen das Konzept in C# besser oder man kann es über die Analogien in C# leichter verstehen, was der Pointer als Referenz in C++ tut.

    Wenn es nach mir geht, brauchst du solche Artikel nicht unbedingt schreiben (was wohl daran liegt, das ich nichts neues erfahre). Aber für Leute, welche von C# auf C++ umsteigen wollen, ist dies sicher sehr hilfreich.

    P.S. Ich würde lieber möglichst bald erfahren, was du mit den Modeldatein vor hast (in Bezug auf das Kommentar im C++ Artikel) 😛

  2. Für mich liegt das Problem nicht bei den Pointer sondern bei dem Intellisense von Miscrosoft VS Express für C++.

    In C# wird, egal was man eintippt, sofort das Popup aufgemacht und sieht alles. In C++ / DirectX gibt es so lange Namen wie DXGI_FORMAT_R32G32B32_FLOAT oder D3D11_INPUT_ELEMENT_DESC. Für sowas geht das Intellisense nicht auf was ich ziemlich blöd finde. Ich muss dann immer erst den Fehler suchen was das voranschreiten ziemlich verzögert. Auf der anderen Seite ist VSE an anderen Stellen sehr komfortabel das ich darauf nicht verzichten möchte.

    Im Grunde genommen ist das mein größtes Problem warum ich mich um C++ drücke.

    Was ich damit sagen will: Der Unterschied zischen C++ und C# dürfte jedem klar werden wenn man sich genauer mit C++ beschäftigt und wenn du C++ Artikel schreibst, womöglich eine ganze Tutorial Reihe wie „C++ 101“ oder so, dann kommt das Verständnis von ganz alleine.

    Trotz alledem sind deine Artikel immer sehr einfach zu verstehen und informativ und ich freue mich über jeden C# / C++ Artikel den du verfasst.

    P.S. Gibt trotzdem den einen oder anderen der dieser Artikel sehr geholfen hat 🙂 Mach weiter so!

  3. Mal wieder ein toller Eintrag! Ich habe selbst die Erfahrung gemacht dass rekursive Funktionen ohne Clone()/Copy() nicht funktioniert haben…
    Der eine Codeblock beim Referenzkopieren in C# ist übrigens nicht formatiert, solltest Du noch korregieren. 🙂

    Warum willst Du eigentlich zwingend DX11 und Verbindung mit C++ verwenden? SlimDX ist doch ein sehr schoener Wrapper, z.B. der Computeshader laeuft in dieser Verbindung auch sehr schoen 😉

    • Danke für den Hinweis mit der Formatierung, habe ich sofort korrigiert.

      Ich möchte nicht zwingend DX11/C++ machen, aber es bietet sich an und hat ein paar Vorteile: Libraries wie Box2D und Bullet (und einige andere) können nativ verwendet werden und man ist nicht auf einen Wrapper angewiesen. Ausserdem ist der Code portabler und kann einfacher für andere Systeme wiederverwendet werden.

      Nachteil ist natürlich auf jeden Fall eine größere Hürde für C++ 🙂 Aber auch das kann man meistern 😉

      Übrigens: Ich persönlich finde SharpDX deutlich besser als SlimDX, nicht nur wegen der deutlich höheren Geschwindigkeit.

  4. Also jetzt auch auf C++ unterwegs. Deine Beiträge zum Terrain „missbrauche“ ich dafür schon länger, und zwar um sie auf Dx11/C++ zu portieren. Das ist gerade für den Anfänger schon ein rieser Aufwand, da man selbst vermeintlich triviale Sachen selbst schreiben muss. Für den Anfang wären entsprechende Beiträge hier im Blog doch kein schlechter Ansatz.

  5. Schöner Artikel, danke! Das einzige was mich stutzig gemacht hat ist, wofür im C++ Code ganz zum Schluss die Function „getchar()“ da ist? Ist es dafür da um wie bei std::cin.get() dafür zu sorgen, dass das Konsolenfenster nicht verschwindet?

  6. Der Artikel hat mir sehr geholfen! Ich habe längere Zeit mit C++ programmiert und muss mich jetzt mit C# Code auseinandersetzen. Ich war sehr verwunder darüber, dass Objekte in Massen kopiert werden… Ich hatte schon den selben Test gemacht und es selber herausgefunden aber das ist der erste Artikel der es auf den Punkt bringt und mir die notwendige Gewissheit gibt. Danke!

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

%d Bloggern gefällt das: