D – die neue Programmiersprache mit C++-Wurzeln

Seite 2: Steile Lernkurve

Inhaltsverzeichnis

Bei der Entwicklung von D standen dementsprechend anspruchsvolle Ziele auf der Agenda. Es war von Reduktion der Softwareentwicklungskosten um mindestens 10 Prozent die Rede. Eine höhere Portabilität sollte für das Erstellen von Anwendungen sorgen, die auf verschiedenen Plattformen laufen, dabei schlank bleiben und ohne umfangreiche Laufzeitumgebung auskommen. Ein weiteres Ziel bestand in einem Multiparadigmenansatz mit Unterstützung generischer, objektorientierter, imperativer und strukturierter Sprachstile. Zu den essenziellen Anforderungen gehörte des Weiteren „Vereinfachung“, und damit ist sowohl die steile Lernkurve für C/C++-Entwickler gemeint als auch die Verfügbarkeit einer kontextfreien Grammatik für die problemlose Entwicklung entsprechender Werkzeuge. Die Unterstützung von Sprach-Features für systemnahe Programmierung sowie die Anbindung von C-basierten Binärbibliotheken sollten für Kompatibilität mit existierenden Welten sorgen. Nicht zu vergessen die Integration neuester Errungenschaften aus der Welt der Sprachdesigner und des Software-Engineering wie kontraktbasierte Programmierung, Unit-Tests oder Internationalisierung. Kurz gesagt, ein Rundum-Wohlfühlpaket für C++-Entwickler.

Tatsächlich bestätigt der erste Eindruck, dass die D-Erfinder ihre Hausaufgaben erledigt haben. D bezeichnet sich als (im Vergleich zu C/C++) leicht erlernbare Sprache für die System- und Anwendungsprogrammierung, die zwar eine höhere Abstraktionsstufe als C++ aufweist, sich aber dennoch für das Erstellen hochperformanten und hardwarenahen Codes eignet. Im Gegensatz zu anderen modernen Programmiersprachen wie Ruby, Java oder C# bietet D weder eine virtuelle Maschine noch eine interpretierte Skriptsprache. Laut den Sprachschöpfern erlaubt das pragmatische Design der Programmiersprache, hoch optimierende native Übersetzer bereitzustellen. Allerdings spräche nichts gegen eine Implementierung von D auf Basis einer virtuellen Maschine. In der Dokumentation (siehe |#OQ]Onlinequellen[/anchorlink]) ist davon die Rede, dass sich mit D mittlere bis sehr große Anwendungen im Team entwickeln lassen. Allerdings sind solche Aussagen erfahrungsgemäß mit Vorsicht zu genießen, weil die gewählte Programmiersprache in dieser Hinsicht zwar eine wichtige Rolle spielt, aber viele weitere Faktoren mindestens ähnliche Wertigkeit haben.

Um einen ersten Eindruck von einer Programmiersprache zu bekommen, gehört seit den Zeiten von Kernighan und Ritchie das „Hello World“-Programm zu den unvermeidlichen Fingerübungen:

import std.stdio;
int main ( ) {
writefln ("Hello iX");
return 0;
}

Im Beispiel fällt sofort das import-Statement ins Auge, das fehlerträchtiges Inkludieren von Header-Dateien à la C durch ein Modulkonzept ersetzt. Im Modul std.stdio befinden sich Routinen für die Ein- und Ausgabe, deren Implementierung in der Standardbibliothek Phobos erfolgt. Zu diesen und anderen Eigenschaften später mehr. Wie üblich repräsentiert main() den Einstieg ins Hauptprogramm. Mit writefln() erfolgt die konsolenbasierte Ausgabe mit Zeilenumbruch.

Will man dieses Programm übersetzen, gibt es zwei Alternativen: den Digital Mars D Compiler DMD oder das D-Frontend für den GCC. Beim konsolenbasierten dmd etwa stößt folgende Anweisung das Übersetzen an: dmd hello.d. Ergebnis des Übersetzungslaufes ist ein ausführbares Programm.

Eine Abhandlung aller Sprachmerkmale von D würde den Rahmen eines Artikels sprengen, sodass hier nur ein Streifzug durch die schöne, neue D-Welt erfolgen kann. Das Hauptaugenmerk liegt dabei auf Features, mit denen sich D eindeutig von C++ unterscheidet.

Insgesamt bieten die in D vorhandenen objektorientierten Konzepte nur wenig Überraschungsmomente für OO-Veteranen. Gegenüber C++ existieren allerdings einige sinnvolle Einschränkungen und Erweiterungen. Hier ein paar Beispiele: Der fehlerträchtigen Mehrfachimplementierungsvererbung von C++ stellt D eine einfache Implementierungsvererbung und zudem ein Schnittstellenkonzept gegenüber. Vor jeder Vereinbarung darf ein Sichtbarkeitsattribut – public, private, protected – stehen. Mit final annotierte Methoden lassen sich in Unterklassen nicht virtuell überschreiben. Zur Definition von Konstruktoren nutzt der D-Programmierer das Schlüsselwort this und für Destruktoren entsprechend ~this. Statische Methoden und Variablen sind unabhängig von Instanzen verfügbar. Deren Kennzeichnung erfolgt über ein zusätzliches static-Attribut. Um das alles zu veranschaulichen, implementiert |#l1]Listing 1[/anchorlink] eine abstrakte Basisklasse Planet inklusive der Schnittstelle IDraw. In der konkreten Unterklasse Earth ruft der Konstruktor (this) rekursiv den Konstruktor der Elternklasse auf (super).

Das Ergebnis des Programmlaufs lautet:

-O-
Earth
Instances = 1
Vogon Alarm!

Erfahrene C/C++-Programmierer haben sich zwar mittlerweile an das umständliche und fehlerbehaftete Hantieren mit Header-Dateien gewöhnt, blicken aber bisweilen sehnsüchtig auf die Modulkonzepte von Programmiersprachen wie C#. In D ersetzt ein wohl der Java-Welt entlehntes Modulkonzept die schwerfällige Aufteilung in Definitionen und Deklarationen.

Zur Vereinbarung eines neuen Moduls genĂĽgt eine Modul-Vereinbarung am Anfang des Quelltextes:

module A;
void f1();
void f2();

Die Integration von Deklarationen aus einem anderen Modul verläuft über Import-Anweisungen:

module B;
import A;
f1(); // Aufruf von A.f1()

Dabei demonstrieren die Code-Auszüge nur die Spitze des Eisberges. Entwickler können zum Beispiel ganze Hierarchien von Modulen über öffentliche Imports integrieren, statt eines ganzen Moduls nur eine Teilmenge davon importieren, Deklarationen mit Aliasnamen versehen oder die volle Qualifikation importierter Entitäten über statische Imports erzwingen.

Für die Speicherverwaltung bietet D drei unterschiedliche Methoden. Statische Daten liegen auf dem Datensegment, Stack-Daten alloziert und dealloziert die Laufzeitumgebung automatisch beim Betreten oder Verlassen von Aufrufblöcken. Zusätzlich existiert eine automatische Speicherbereinigung (Garbage Collector) für dynamische Heap-Daten. Wie üblich, ist die Speicherverwaltung auf dem Stack effizienter, die auf dem Heap flexibler bezüglich der Lebenszeit von Objekten. Mit dem Operator new() allozierte Objekte etwa landen grundsätzlich auf dem Heap. Ähnlich wie bei C++ kann der Entwickler allerdings den new- und den delete-Operator überschreiben, um die Speicherverwaltung in die eigene Hand zu nehmen: Die Beispielanwendung nutzt in C-Manier die Funktion std.c.stdlib.malloc, um manuell einen Speicherblock zu reservieren (new), und gibt diesen Block dem Garbage Collector bekannt (std.gc.addRange). Die Freigabe erfolgt zunächst mit dem Aufruf der delete-Methode über std.gc.removeRange und danach über std.c.stdlib.free. Das aber nur als Illustration für die unverbesserlichen Control-Freaks |#l2](Listing 2)[/anchorlink] .