Referenz oder doch Zeiger?
In C oder C++ würde man stets einen Zeiger übergeben, sofern man in der zu übergebenen Methode/Funktion diesem Parameter einen neuen Wert zuweisen würde. Denn ansonsten würde dieser neu zugewiesene Wert am Ende der Methode/Funktion wieder verfallen.
Es ist nicht einmal so, dass man das tun würde, sondern das man es tun muss, da man in C/C++ keine andere Wahl hat.
In D und auch in C# hat man dafür das Keyword “ref”, wobei man in D dennoch noch Zeiger benutzen kann.
Bei einem Benchmark, welches denn nun schneller sei, stellte ich fest, dass kaum merkbare Unterschiede festzustellen sind und ref als Syntax wirklich angenehmer wirkt.
Doch wofür dann noch, außer natürlich für C Kompatibilität, in D Zeiger benutzen?
Darüber hatte ich auch einige Zeit nach gegrübelt und schließlich diese Gedankengänge verworfen, da das export- und importieren von C Funktionen in D einer der wertvollsten Eigenschaften ist, die die Sprache bietet.
Doch heute traf ich auf eine Gelegenheit wo Zeiger wirklich die einzige gute Wahl waren.
Ich hatte eine Methode mit der ich einen Status abfragen konnte und wollte als optionalen Parameter anbieten, dass, sofern der Status abweicht, der wirkliche Status in diesem Parameter gespeichert wird und per Referenz zurückgegeben werden kann.
Natürlich wählte ich sofort ref. Und damit es optional ist wies ich dieser Parameter schließlich den Default Wert “null” zu, in etwa so:
bool valid_status(const App app, ref Status real_status = null) {
Doch endete mit einem Error, dass “real_status” kein lvalue sei. Das ist auch korrekt. Und damit war die Möglichkeit einen optionalen Parameter anzubieten dahin. Nun gab es vorerst nur einen weiteren Weg: zwei Methoden, einmal mit und einmal ohne zweiten Parameter. Eine typische Java Lösung.
Das gefiel mir nicht. Doch dann kam mir der Gedanke an Zeiger wieder und et voilà: es funktioniert bestens so:
bool valid_status(const App app, Status* real_status = null) {
In dem Methodenrumpf muss nun natürlich statt einfach: real_status = real_app_status; ein Sternchen vor “real_status”, um es als Zeiger zu kennzeichnen, also so: *real_status = real_app_status; aber das war es auch schon
Man sollte allerdings auf der Hut sein. Viele D Konstrukte besitzen eine “.ptr” property welche einen C Zeiger des Konstrukts zurückgibt, damit C Kompatibilität gewährleistet ist.
Es kann vorkommen, dass man nun etwas verwirrt ist und/oder unbedacht mit “construct.ptr” und “&construct” arbeitet.
Mir fiel es heute auf, als ich mit OpenGL hantierte und einem Buffer meine Vertices übergeben sollte, welche sich aus einem Vertex Array Vertex[] zusammensetzen.
Ich übergab glBufferData “&vertices” und wunderte mich, dass ich keine (korrekte) Ausgabe erhielt.
Doch dann erkannte ich, dass ich die “.ptr” property verwenden muss.
Eine kurze Erklärung dazu:
Arrays in C/C++ sind von sich aus im eigentlichen Sinne bereits Pointer und bei deren Übergabe als Parameter wird ein lediglich ein Pointer auf das erste Element dieses Arrays übergeben. Das ist auch der Grund dafür, dass man in sämtlichen OpenGL Tutorials (zumindest denen die C/C++ benutzen), auch immer nur vertices, anstatt &vertices (oder eben in D auch vertices.ptr) liest.
Eine kleine Veranschaulichung des ganzen:
import std.stdio; struct Vertex { float x, y, z; this(float x, float y, float z = 0.0) { this.x = x; this.y = y; this.z = z; } } void main() { Vertex[] data; data ~= Vertex(10, 10); data ~= Vertex(100, 10); data ~= Vertex(10, 100); data ~= Vertex(100, 100); auto ptr1 = data.ptr; auto ptr2 = &data; writeln(typeid(typeof(ptr1))); writeln(typeid(typeof(ptr2))); }
ergibt:
D:\D\D_Scripts\c_to_d_interfacing\SDL_1.3\tests>pointer
pointer.Vertex*
pointer.Vertex[]*
Kein Wunder also, OpenGL brauchte schließlich einen Pointer auf das gesamte Array. Und da wir in D und nicht in C sind, reichte ein “&vertices” oder gar nur “vertices” nicht aus.
Ich wollte es auch nur gesagt haben, es gibt sicher einige Situationen, wo dieses Wissen hilfreich sein könnte.
Ich weiße dich mal daraufhin das der letzte Satz so nicht ganz stimmt, &vertices liefert ein D Array, also mit größen Information usw, in C sind Arrays reine Daten, deswegen die .ptr Eigenschaft um darauf zugreifen zu können.
Außerdem solltest du dir auch nochmal Value Types vs Reference Types anschauen, unter D sind Klassen und Arrays immer Referenztypen, Structs und Primary Typen sind Value Types.
Wie kommst du darauf, das ich das nicht wüsste? Habe doch seinerzeit im Forum schon philosophiert, ob ich
const refbei Klassenübergaben machen muss, weil ich C++ so gewöhnt war.So war das eig. auch gemeint.
Du siehst ja das mein geposteter Code folgendes ausgibt:
pointer.Vertex*pointer.Vertex[]*
Also einmal einen Zeiger, so wie es in C bei Array Übergaben üblich ist, da bei Array Übergaben nur ein Pointer auf das erste Element übergeben wird.
Und einmal ein Zeiger auf ein Array.
Ja und nein, der Punkt ist unter C/C++ “sind” Arrays Pointer auf die Daten im Speicher, unter D ist ein Array ein Objekt, also ist data in dem Fall eine Referenz auf ein Objekt im Speicher, wenn du davon den Pointer willst bekommst du einen Zeiger auf das Objekt im Speicher. Das ist aber ein spezifisches D Objekt, C erwartet ja die Rohdaten als Array Information und kann mit dem D Objekt ja nix anfangen. .ptr liefert dann von dem Array Objekt von D den internen Zeiger auf die rohen Daten das ist ein Attribut der Array Klasse von D.
Vereinfacht:
class Array{
void* ptr;
int length;
//....
}
auto data = Array();
Das ist das was D intern macht, mir ist klar das du das weist aber ich wollte die Formulierung mal unmissverständlich machen das das jeder richtig versteht auch wenn er damit noch keine große Erfahrung hat.
Value Types vs Reference Types gehören der Vollständigkeit noch erwähnt weil nicht immer muss man in D ref mitangeben, ein Link zu den D Docs bezüglich Pointer und Referenzen wäre vllt auch noch nützlich.
Ich habe morgen ‘ne Klausur und muss bis Montag noch eine Ausarbeitung abgeben; würdest du dich bereits erklären, die etwaigen Artikel stellen anzupassen und zu erweitern?