SucheBlog abonnierenVerwaltung des BlogsKategorienKontaktMarkus Brückner |
Samstag, 1. Oktober 2005Bereichsgeprüfte Datentypen in C++Nach all dem privaten, nichttechnischen Kram mal wieder etwas technisches. Schließlich muss man den Unmengen von Lesern (so 3-5) ja auch was bieten. Statt Sex, Drugs and Rock'n'Roll gibt es mal wieder C++ und Templates. Aber von vorn... In einem Anfall von Langeweile habe ich mich mal ein wenig nach Links zur Programmiersprache Ada umgeschaut. Diese wird generell als die Sprache der Wahl zur Entwicklung sicherer Software gehandelt, da sie viele eingebaute Möglichkeiten bietet, Korrektheit im Programm sicherzustellen. Ein nettes Feature, welches mir aufgefallen ist sind bereichsgeprüfte Datentypen. Man kann in Ada einen numerischen Datentyp in folgender Form definieren:
type Time_Of_Day is range 0..86399; Das definiert einen Typ Time_Of_Day, der Werte von 0 bis 86399 annehmen kann. Das hat mich dazu inspiriert, mal wieder ein wenig mit Templates rumzuspielen um sowas in C++ nachzubauen. Ziel war es, möglichst wenig Speicherplatz pro bereichsgeprüfter Variable zu verwenden und möglichst wenig Laufzeitoverhead zu haben. Grundsätzlich kann man einen bereichsgeprüften Datentyp auf zwei Arten implementieren: einmal können die Grenzen als Membervariablen in jedem Objekt abgelegt sein und zum anderen können diese als Templateparameter übergeben werden. Die Entscheidung zwischen beiden Varianten ist eine Abwägung zwischen Codegröße und Datengröße. Die Templatevariante führt bei vielen verschiedenen Typen mit unterschiedlichen Grenzen zu einer erheblichen Vergrößerung des Quellcodes, da für jedes Paar von Grenzen eine Templateinstanz angelegt wird. Auf der anderen Seite führt die Haltung der Grenzen im Objekt selbst bei vielen Objekten zum einem erheblichen Speicherbedarf, da nun jedes Objekt Platz für die Werte der Grenzen braucht. Da es das Ziel war, möglichst wenig Speicher für die Objekte zu verwenden, habe ich mich für die Templatevariante entschieden. Das Template wird wie folgt verwendet: RangeChecked<int, 0, 10> var(5). Hiermit wird eine Variable var mit dem Interval [0,10] angelegt und auf den Wert 5 initialisiert. Da sich 5 doch recht klar in [0,10] befindet, geht das auch gut. Befände sich der Initialisierungswert außerhalb, würde eine Exception vom Typ RangeError geworfen. Das Template stellt verschiedene Operationen für numerische Datentypen (+, -, /, = und so weiter) zur Verfügung. Zu beachten ist hierbei, da immer nur die Zuweisungen geprüft sind. Überschreitet also der Wert eines Ergebnistyps innerhalb einer Gleichung kurzzeitig den Bereich, so passiert nichts, da erst der Zuweisungsoperator prüft. val = 10 + 5 - 7; wäre im obigen Beispiel also kein Problem, da das Endergbenis bei der Zuweisung wieder in [0,10] liegt. Eine Zuweisung Variablen unterschiedlicher Intervalle ist auch möglich, solange der zugewiesene Wert im Intervall des Ziels liegt. Als Templateparameter kommen prinzipiell numerische Typen in Betracht. Jedoch gibt es hier eine Ausnahme: Fließkommazahlen können keine Verwendung finden. Der Grund hierfür liegt in den Regeln des C++-Standards. Dieser spricht folgendes zu non-type parameters: 3 A non-type template-parameter shall have one of the following (optionally cv-qualified) types: Fließkommazahlen als Templateparameter sind also nicht erlaubt (hauptsächlich der erste Teil mit den integral types ist interessant. Eine genaue Begründung dafür kann ich nicht geben. Der Versuch einer Diskussion findet sich in de.comp.lang.iso-c++. Vorherrschende Meinung scheint zu sein, daß sich bei der Instanziierung von Templates mit Fließkommaparametern gewisse Probleme mit der Genauigkeit der Zahlendarstellung ergeben. Das Problem der nicht exakten Darstellbarkeit vieler Fließkommanzahlen in Computern wird bei Vergleichen in Programmen typischerweise dadurch umgangen, daß zwei Werte als gleich definiert werden, wenn sie innerhalb einer Schwelle Epsilon liegen. So vermeidet man Probleme durch Rundungsfehler etc. Im Fall von Templateparametern ist das allerdings nicht so einfach. Dort gibt es nur Gleichheit oder Ungleichheit. So könnte eine etwas groteske Situation entstehen: für folgendes Template existieren die beiden Instanziierungen a und b: template <double d> class Test
{
};
Test<1.0> a;
Test<0.7+0.3> b;
Sind diese beiden Instanzen vom selben Typ? Der gesunde Menschenverstand würde sagen "Ja, denn 0.3+0.7==1.0.". Ein Compiler kann das eventuell anders sehen. Da weder 0.3, noch 0.7 auf gängigen Architekturen exakt binär darstellbar sind, könnte es durch Rundungsfehler dazu kommen, daß statt 1.0 eben nur 0.9999999 entsteht. Verdammt nah dran an der 1.0, aber eben nicht gleich. Eine Verwendung von b an einer Stelle, wo Test<1.0> erwartet wird müßte also scheitern. In dem verlinkten Thread werden einige Ansätz diskutiert, wie dieses Problem zu lösen sei (symbolisch rechnen, nur Literale als Parameter erlauben). Alle diese Ansätze haben gewisse Probleme (Aufwand, Unterschiede zwischen verschiedenen Compilern etc.). Man darf also gespannt sein, ob sich das Standardisierungskommitee dieses Problems annimmt und wenn ja, wie die Lösung aussieht. So, nach all dem Geblubber noch schnell das Template zum Runterladen: RangeChecked.hxx. Der Code ist wieder public domain. Die Verwendung ist im Kopf der Datei nochmal erklärt.
(Seite 1 von 1, insgesamt 1 Einträge)
|