D: Namespaces
Namespaces? So etwas hat D doch gar nicht. Dafür gibt es Module.
Das ist richtig.
Es mag den einen oder anderen geben, der sich aus C# oder C++ an namespaces (in deutsch: Namensräume) gewöhnt hat und diese nun in D vermisst.
Walter Bright, der Erfinder von D, ist der Meinung, dass Module die Aufgabe von namespaces vollständig übernehmen.
Bis zu einem gewissen Grad hat er auch recht, allerdings gibt es einige Dinge, die man vermisst, wenn man sie von namespaces her kennt.
Ein Modul z.B. kann keine Funktionen oder Variablen nach außen hin kapseln, mit der Möglichkeit, später per Prefix oder (in C++ durch using namespace …) je nach Wahl gänzlich verfügbar zu machen. Die Betonung liegt dabei auf später & auf Prefix. Man kann auch nicht explizit verlangen, dass das Modul als namespace dienen soll, denn dann müsste man irgendwie erzwingen, dass das Modul per static import importiert wird.
Was man aber tun könnte: statische Variablen in einer (finalen) Klasse kapseln. Allerdings: was macht man bei Funktionen?
More »
D: Benchmark für Parameter storage classes
Eventuell kennen manche das aus C++.
Objekte werden bei Parameter Übergabe kopiert, wenn nicht anders angegeben. Deswegen ist es in C++ Standard geworden, Objekte von denen man nur bestimmte Daten braucht, als Referenz zu übergeben. Und damit sie Konstant und damit nicht innerhalb dieser Methode/Funktion unglücklicherweise aus Versehen geändert wird, schreiben wir auch const davor.
Etwa so:
void Foo::get_something(const Bar & bar) const { std::cout << "Punkte: " << b.get_points() << std::endl; }
In D ist es dagegen wie in Java oder C#: Objekte werden Standardmäßig als (Konstante) Referenz übergeben, auch genannt call by reference.
Allerdings nicht so bei Structs. Diese werden wie gewohnt als Kopie übergeben (call by value) und können damit einiges an Speicher und Laufzeit fressen.
Also bietet es sich hier an, wie in C++ zu verfahren und mit Referenzen statt mit Kopien zu arbeiten, um effizientere Programme zu schreiben. Aber wie groß ist eig. der Unterschied in D? Wegen dieser Frage habe ich mir in den letzten Tagen folgendes Benchmark Script geschrieben und diverse Parameter Storage Klassen (in*, out, ref, const, const ref, ref const) getestet, aber auch die normale Übergabe per Kopie.
Dabei ist out als nicht wirklich ernst zunehmender Konkurrent angetreten. Warum? Weil out alles auf die Initialisierungs- bzw Ausgansform zurücksetzt. So auch bei diesem Test, bei dem eigentlich x mal (drei Durchgänge á 500.000, sowie drei Durchgänge á 100.000) ein Array eines Struct mit 25.000 Elementen befüllt und dann intern durchgegangen und benutzt wurde.
Mittels out wurde natürlich wieder alles geleert. out kann somit als leerer Vergleich angesehen werden, sprich wie lange das Struct ohne 25.000 Array Elemente gebraucht hätte.
*Mir ist klar, das in nur ein alias für const scope ist, aber es ist hier dennoch der Vollständigkeitshalber & weil mich evtl. auftretende Unterschiede interessierten.
Benchmark Script:
import std.stdio; import core.time; class Timer { private TickDuration _timer; private long _starttime; private ubyte _count; void start() { _count++; _starttime = _timer.currSystemTick().msecs; } double stop() { long time = _timer.currSystemTick().msecs - _starttime; float ticksPerSec = 1000.f; /* writefln("[%d] Elapsed time = %d.%010d seconds", _count, cast(int) (time / ticksPerSec), cast(int) (time % ticksPerSec) ); */ return time / ticksPerSec; } } struct Node { int[] array; this(const int n) { for (uint i = 0; i < n; i++) { array ~= i; } } void doStuff() const { foreach (const int i; array) { int k = i * 2; } } } class A { this(Node n) { static bool cout = false; if (!cout) { cout = true; writeln("normal: ", typeid(n)); } n.doStuff(); } } class B { this(const Node n) { static bool cout = false; if (!cout) { cout = true; writeln("const: ", typeid(n)); } n.doStuff(); } } class C { this(in Node n) { static bool cout = false; if (!cout) { cout = true; writeln("in: ", typeid(n)); } n.doStuff(); } } class D { this(ref Node n) { static bool cout = false; if (!cout) { cout = true; writeln("ref: ", typeid(n)); } n.doStuff(); } } class E { this(const ref Node n) { static bool cout = false; if (!cout) { cout = true; writeln("const ref: ", typeid(n)); } n.doStuff(); } } class F { this(ref const Node n) { static bool cout = false; if (!cout) { cout = true; writeln("ref const: ", typeid(n)); } n.doStuff(); } } class G { this(out Node n) { static bool cout = false; if (!cout) { cout = true; writeln("out: ", typeid(n)); } n.doStuff(); } } void main() { immutable ushort num = 25000; immutable uint c = 500000; Timer t = new Timer(); double runtime = 0.0; for (uint j = 0; j < c; j++) { t.start(); Node n = Node(num); A obj = new A(n); runtime += t.stop(); } writefln("Elapsed Time: total: %f sec. average: %f sec.", runtime, runtime / c); runtime = 0.0; for (uint j = 0; j < c; j++) { t.start(); Node n = Node(num); B obj = new B(n); runtime += t.stop(); } writefln("Elapsed Time: total: %f sec. average: %f sec.", runtime, runtime / c); runtime = 0.0; for (uint j = 0; j < c; j++) { t.start(); Node n = Node(num); C obj = new C(n); runtime += t.stop(); } writefln("Elapsed Time: total: %f sec. average: %f sec.", runtime, runtime / c); runtime = 0.0; for (uint j = 0; j < c; j++) { t.start(); Node n = Node(num); D obj = new D(n); runtime += t.stop(); } writefln("Elapsed Time: total: %f sec. average: %f sec.", runtime, runtime / c); runtime = 0.0; for (uint j = 0; j < c; j++) { t.start(); Node n = Node(num); E obj = new E(n); runtime += t.stop(); } writefln("Elapsed Time: total: %f sec. average: %f sec.", runtime, runtime / c); runtime = 0.0; for (uint j = 0; j < c; j++) { t.start(); Node n = Node(num); F obj = new F(n); runtime += t.stop(); } writefln("Elapsed Time: total: %f sec. average: %f sec.", runtime, runtime / c); runtime = 0.0; for (uint j = 0; j < c; j++) { t.start(); Node n = Node(num); G obj = new G(n); runtime += t.stop(); } writefln("Elapsed Time: total: %f sec. average: %f sec.", runtime, runtime / c); }
D: Array-Extensions
Jeder, der mit D anfängt zu programmieren, freut sich über die wunderbaren Array-Funktionen die D von Haus aus mitbringt. Anders wie in C++ bestehen Arrays nämlich nicht mehr einfach aus einem einfachen Datenfeld, sondern beinhalten Informationen über die aktuelle Länge und auch ein paar nützliche Operationen wie den Slice-Operator.
Diese Feature sind ja auch schön und gut, wären aber noch nicht Grund genug, dass im Phobos-Framework keine einzige Klasse wie vector oder list zu finden sind. Nun ja, der wahre Grund hierfür ist, dass D noch ein weiteres Feature bietet: die sogenannten Array-Extensions. Es handelt sich dabei um Funktionen, die das Array in ihrer Funktionalität erweitert und man gleichzeitig den Eindruck gewinnt, dass die Funktion eine Memberfunktion des Arrays ist. Dabei ist folgende Regel zu beachten: Je allgemeiner man die Funktion implementiert ist, desto mehr Arrays haben die Möglichkeit, die Funktion zu nutzen. Es empfiehlt sich also generisch, sprich mit Templates zu programmieren.
More »
D: gleichnamige Methode als Template führt zu Konflikt
Vor ein paar Tagen fiel mir ein Bug oder zumindest ein merkwürdiges Verhalten in D auf.
Jeder kennt das nette Feature, dass zwei Methoden den gleichen Namen tragen dürfen, sofern sie sich in ihren Parametern (und/oder return Typ) unterscheiden.
Außerdem ist es möglich, das eine der gleich benannten Methoden ein Template ist.
Bspw. möchte man eine Koordinaten oder Rechteck Klasse haben, in der man explizit ein short für einen x oder y Punkt zurückerwartet, aber möchte dem Benutzer das manuelle umcasten von bspw. float in short Werte abnehmen.
Man definiert also eine Template Methode, welche einen impliziten Typ deklariert den man später in der Methode zu short castet (idealerweise nur, wenn es nicht schon ein short ist
).
More »
How to: Templates und Operator Overloading
In diesem Artikel möchte ich die Nützlichkeit von Templates (vor allen was Makro Anhänger in C++ angeht) und dem überschreiben von (Arithmetischen) Operatoren in C++ und D zeigen.
Ich werde ein bei mir aktuelles Bsp. aufgreifen und dann einen Lösungsweg Stück für Stück in D entwickeln, aber auch den C++ Äquivalent dazu.
Vielleicht kennt jemand das Problem schon:
Angenommen, man hat eine derartige Template Struktur
alias Vector2!(float) Vector2f;
alias Vector2!(short) Vector2s;
struct Vector2(T) {
T x, y;
this(in T x, in T y) {
this.x = x;
this.y = y;
}}
Und des Weiteren hat man ein Element wie Vector2f v1 = Vector2f(1.5, 2.3); und muss es, aus irgendeinem Grund, in einen Vector2s umwandeln/casten.
Wie stellt man das am besten an?
More »