C++Guns – RoboBlog blogging the bot

04.09.2017

C++ Guns - Semantik und concepts - Part 3

Filed under: Allgemein — Tags: — Thomas @ 17:09

Kommen wir nun zu den concepts. Wie anfangs erwähnt, las ich das Paper [1].
Ich erlaube mir hier ein paar Ausschnitte davon zu kommentieren, um meine Motivation zu begründen.

A concept has semantics; it means something; it is not just a
set of unrelated operations and types. Without an idea of what operations mean and how they relate to each other we cannot write generic code that works for all appropriate types.

Ein Konzept bedeutet also irgendetwas. Wenn man sich hinsetzt und irgendetwas programmiert, hat man irgendetwas im Kopf, was es bedeuten soll. Aber diese Bedeutung können wir nur schwer in Code formulieren. Das liegt erfahrungsgemäß nicht nur an der Sprache und ihre Mittel, hauptsächlich ist es der Programmierer, der nicht weiß was er will.
So wird viel ausprobiert und viel Code umgeschrieben. Und dabei ein Haufen Bugs eingebaut, die vermeidbar gewesen wären, hätte man sich nur mal kurz überlegt, was man überhaupt will.

It follows that a guarantee that all types accepted by concept checking
will work correctly is impossible: they may have exactly the syntactic properties required, but have the wrong semantics. This is nothing new: Similarly, a function taking a double can
interpret it differently from what the caller expects. Consider set_speed(4.5). What does this mean? Is 4.5 supposed to be in m/s or maybe miles/hour? Is 4.5 an absolute value, a delta to the current speed, or possibly a factor of change?

Genau hier will ich ansetzen und widersprechen.
Der Programmierer der Funktion set_speed hat in seinem Kopf gewusst, was die Funktion machen soll, und was der Funktionsparameter bedeutet. Das muss so sein, sonst hätte er sie nicht programmieren können. Er hat diese Dinge aber nicht in Code formuliert. Wenn zum Beispiel die Funktion set_speed als Argument kein double entgegen nimmt, sondern ein Type namens Speed, welcher intern auf den Physikalischen Einheiten Meter pro Sekunde definiert ist, dann ist es nicht möglich aus versehen eine Geschwindigkeit in miles/hours zu übergeben, ohne dass der Compiler aktiv wird.
Selbes gilt für eine Geschwindigkeitsänderung welche z.B. durch den Typ DeltaSpeed ausgedrückt werden könnte, oder eine Änderung durch Skalierung.

Genau darum geht es. Um die Bedeutung von Variablen. Um die Bedeutung des Variablentypes, der mittels concepts zur Compilezeit überprüft werden kann.

I suspect that perfect checking for all code will forever elude us;

Hier bin ich vom Gegenteil überzeugt. Sobald alle Informationen ohne Fehler für den Compiler in auswertbarer Form vorliegen, muss der Compiler immer ein Fehler liefern, sobald man Schwachsinn programmiert hat.
Es macht in keinem Universum Sinn, Geschwindigkeit und Abstand zu addieren. Hier wird der Compiler sofort aktiv.
Auch Algorithmen müssen vom Compiler überprüfbar sein. Wenn alle Ein- und Ausgabedaten durch Prädikate beschrieben werden, muss der Compiler Fehler im Algorithmus finden.

Wenn aber jemand den Abstand Erde-Sonne in Millimeter berechnen will, ist das natürlich kein Compilezeitfehler.

Im nächsten Teil gibt es wieder C++ Code.

[1] Concepts: The Future of Generic Programming or How to design good concepts and use them well - Bjarne Stroustrup

C++ Guns - Semantik und concepts - Part 2

Filed under: Allgemein — Tags: , — Thomas @ 14:09

Im letzten Post wollte ich deutlich machen, welche Art von Fehler passieren, wenn zwischen den TypenPoint3D und Vector3D keinen semantischen Unterschied gemacht wird. Also, wenn ein Rechenergebnis von der Bedeutung ein Vektor ist, es aber in einem Punkt Datentyp gespeichert wird.

Die erste Lösung war, einen zusätzlichen Datentyp Vector3D zu erstellen, welcher sich genau wie ein Point3D verhält, aber von der Bedeutung eine andere hat. Dies lässt sich auch noch ohne concepts realisieren. Dazu das selbe Beispiel von Part1 diesmal in FORTRAN. Und im nächsten Teil gibts dann das erste concept.

Da ich nicht weiß ob es in FORTRAN möglich ist, von einem Array zu erben, noch ob man operator()() überladen kann, hier eine Version mit etwas mehr Tipparbeit.

Wichtig ist aber erstmal nur, dass die selbe Art von Fehlermeldung erzeugt werden kann.

conceptpart2.F90
normal = normalenVector(tri)
1
Error: Can't convert TYPE(vector3d_t) to TYPE(point3d_t) at (1)

conceptpart2.F90.gz

module test_m
 implicit none
 
  type Point3D_t
    real(8) :: xyz(3)
  end type

  type Vector3D_t
    real(8) :: xyz(3) 
  end type


  type Triangle_t
      type(Point3D_t) :: points(3)
  end type

  interface operator(-)
    module procedure minus1
  end interface
  
  contains

  function minus1(p1, p2) result(p3)
    implicit none
    type(Point3D_t), intent(in) :: p1, p2
    type(Point3D_t) :: p3

    p3 = Point3D_t( (/ p1%xyz(1)-p2%xyz(1), p1%xyz(2)-p2%xyz(2), p1%xyz(3)-p2%xyz(3) /) )
  end function

  function normalenVector(tri) result(normal)
    implicit none
    type(Triangle_t), intent(in) :: tri
    type(Point3D_t) :: vec1, vec2
    type(Vector3D_t) :: normal
    
      vec1 = tri%points(2)-tri%points(1)
      vec2 = tri%points(3)-tri%points(1)

      normal = Vector3D_t( (/ vec1%xyz(2)*vec2%xyz(3)-vec1%xyz(3)*vec2%xyz(2), &
&                  vec1%xyz(3)*vec2%xyz(1)-vec1%xyz(1)*vec2%xyz(3), &
&                  vec1%xyz(1)*vec2%xyz(2)-vec1%xyz(2)*vec2%xyz(1) /) )
  end function
 
end module

program main
  use test_m
  implicit none
  type(Triangle_t) :: tri
  type(Point3D_t) :: normal
    
  tri = Triangle_t((/ Point3D_t((/0, 0, 0/)), Point3D_t((/10, 0, 0/)), Point3D_t((/10, 10, 0/)) /))
  normal = normalenVector(tri)
end program


01.09.2017

C++ Guns - Semantik und concepts - Part 1

Filed under: Allgemein — Tags: — Thomas @ 10:09

Ich lese gerade das Paper
Concepts: The Future of Generic Programming
or
How to design good concepts and use them well
von Bjarne Stroustrup

http://www.stroustrup.com/good_concepts.pdf

und muss sagen, ich bin restlos begeistert! Die Idee dahinter wird sehr toll beschrieben. Das ist mir am wichtigsten, so dass es möglich ist, die Idee auch bei anderen Programmiersprachen anzuwenden. Natürlich mehr oder weniger schlecht, da die Sprachmittel fehlen. Ich arbeite gerade an einem Beispiel aus meiner Domain welche mit concepts realisiert ist, aber das dauert noch etwas.

Deswegen gibt es heute nur ein einfaches Beispiel aus der angewandten Mathematik, welche den Begriff der Semantik verdeutlichen soll.

Ein Dreieck hat bekanntlich drei Ecken. Also drei Punkte. Auch wenn es nervt, dieser Witz ist sehr treffend. Viele Computerformate bilden ein Dreick als Polygonlinie mit vier Punkten ab! Deren Computer würde explodieren, würde sie C++ mit concepts nutzen ;)

Also, ein Dreieck im 3D Raum hat drei Punkte und jeder Punkt hat drei Skalare: x, y, und z. Es soll der Normalenvector zu einem Dreieck berechnet werden. Dieser steht senkrecht zur Fläche, welche die drei Punkte aufspannen.

Punkte und Dreieck kann man sich leicht vorstellen und anfassen. Wir können sie also schnell als Typen in C++ implementieren:

#ifndef __cpp_aggregate_bases
    #error "You need an actual C++17 compiler with feature P0017R0 'Extension to aggregate initialization' for this example"
#endif
#include <iostream>
#include <array>

using namespace std;

struct Point3D : public array<double, 3> {
};

struct Triangle {
    array<Point3D, 3> points;
};

auto operator-(const Point3D& p1, const Point3D& p2) {
    return Point3D{ p1[0]-p2[0], p1[1]-p2[1], p1[2]-p2[2] };
}

Laut Internet berechnet sich der Normalvekor so:

Das Kreuz-Produkt zweier Vektoren steht senkrecht auf der Fläche, das die beiden Vektoren aufspannen.

Das Kreuzprodukt schütteln wir natürlich im Schlaf aus dem Ärmel. Das haben wir in der Schule rauf und runter geübt. Also los:

auto normalenVector(const Triangle& tri) {
    const auto vec1 = tri.points[1]-tri.points[0];
    const auto vec2 = tri.points[2]-tri.points[0];

    return Point3D{ vec1[1]*vec2[2]-vec1[2]*vec2[1],
                vec1[2]*vec2[0]-vec1[0]*vec2[2],
                vec1[0]*vec2[1]-vec1[1]*vec2[0] };
}

Das ist immer etwas Tipparbeit aber einmal implementiert, immer anwendbar. Oder?

Jetzt fehlt nur noch das Hauptprogramm mit einem kleinen Test:

ostream& operator<<(ostream& s, const Point3D& p) {
    s << "(" << p[0] << " " << p[1] << " " << p[2] << ")";
    return s;
}

int main() {
    Triangle tri{ Point3D{0, 0, 0}, Point3D{10, 0, 0}, Point3D{10, 10, 0} };
    Point3D normal = normalenVector(tri);
    cout << normal << '\n';
}

Compilieren und Ausführen...

Starting part1...
(0 0 100)

Alles klar. Das Ergebnis ist auch richtig. Sind wir fertig? Wo sind die Concepts? Nagut die kommen noch. Aber wo ist der Semantik Fehler? Die Funktion normalenVector() soll einen Vektor zurück geben. Aber wir haben als Rückgabetyp ein Punkt implementiert. Technisch lassen sich sowohl Punkt als auch Vector als drei Skalare implementieren. Aber ihre Bedeutung ist eine ganz andere! Ein Punkt ist eine Position im Raum. Ein Vektor zeigt in eine Richtung.

Jetzt kommt der erste Schritt hin zu Concepts. Die eben erkannte Bedeutung formulieren wir nun im Code. Dazu erstellen wir einen neuen Typ der einen Vektor repräsentiert und die Funktion normalenVector() gibt einen Vektortyp zurück:

struct Vector3D : public array<double, 3> {
};
...
return Vector3D{ ... };

Das neue Programm wäre funktional gleich dem alten. Sogar der Assembler und Binärcode wäre 100% identisch. Das Rechenergebnis natürlich auch. Der Unterschied besteht darin, dass der Compiler nun prüfen kann, ob das, was wir in unserem Programm zum Ausdruck bringen wollen, auch wirklich implementiert haben. Haben wir nicht:

main.cpp: In function ‘int main()’:
main.cpp: error: conversion from ‘Vector3D’ to non-scalar type ‘Point3D’ requested
Point3D normal = normalenVector(tri);

Na, das schaut für den Anfang gar nicht mal so schlecht aus. Das ist nur ein kleiner Schritt Richtung concepts, der sogar in FORTRAN realisierbar ist.

Im nächsten Teil portiere ich diese Idee nach FORTRAN um zu zeigen, wo die Grenze dieser Sprache liegt. Um dann im übernächsten Teil endlich Richtung concepts zu starten.

conceptpart1.gz

22.08.2017

C++ für Anfänger&Profis - Container mit Ganzzahl

Filed under: Allgemein — Tags: — Thomas @ 15:08

Im zweiten Beispiel beschäftigen wir uns damit, wie es möglich ist, beliebig viele Zahlen gleichzeitig zu speichern. Dazu wird ein Container benötigt. C++ bietet hierfür unter anderem die Klasse vector an. Im folgenden Beispiel wird ein vector vom Typ int, der Länge 5 angelegt und ihn mit den Zahlen 1 bis 5 befüllt. Zu beachten ist, dass C++ von 0 ab zählt. Das heißt, die erste Zahl im vector steht an Position 0. Und die fünfte Zahl an Position 4.

Die Variable a vom Typ int kann hingegen nur eine einzige Zahl, zur selben Zeit, speichern.

// Testausgabe
#include <iostream>
// Datencontainer
#include <vector>

using namespace std;

int main() {    
    int a = 1;

    vector<int> b(5);
    b.at(0) = 1;
    b.at(1) = 2;
    b.at(2) = 3;
    b.at(3) = 4;
    b.at(4) = 5;

    cout << "Variable a ist " << a << endl;
    cout << "Variable b bei Index 4 ist " << b.at(4) << endl;
}

In Zeile 9 wird eine ganzzahlige Variable namens a vom Typ int deklariert und mit dem Wert 1 initialisiert.

In Zeile 11 wird ein Container Variable names b vom Typ vector deklaiert und mit Größe von 5 initialisiert. Die einzelnen Elemente werden default initialisiert, in diesem Fall mit dem Wert 0.
In Zeile 12 bis 16 wird jedem Element im vector Container ein spezieller Wert zugewiesen. Der Zugriff auf ein spezielles Element erfolgt über die Funktion at(), welche den Index des Elements entgegen nimmt. Der erste Index ist die 0. Der fünfte Index die 4.
In Zeile 18 und 19 findet eine Kontrollausgabe statt.

Troubleshooting
===============

Der Zugiff auf ein vector Element welches nicht existiert führt zu einer Fehlermeldung und das Beendes des Programms. todo

21.08.2017

C++ für Anfänger&Profis - Hello World! - Details

Filed under: Allgemein — Tags: — Thomas @ 15:08

Wie anfangs erwähnt gibt es für jedes Beispiel eine extra Seite mit Details. Über Performance kann ich bei einem Hello World! Programm wenig sagen, aber dennoch gibt es ein paar Sachen zu erwähnen. Schauen wir uns den Code noch einmal an:

#include <iostream>
#include <vector>

using namespace std;

int main() {
    cout << "Hello World!" << endl;
}

Die #include Anweisungen sind gang und gäbe. Aber bald nicht mehr nötig. Module ersetzen die uralten Proprocessor Direktiven [1].
Der Rückgabetyp von main ist int und nicht void! Das ist so im Standard festgelegt und war noch nie anders.
Die main Funktion darf keine, zwei, oder sogar drei Argumente haben. Das dritte Argument ist Compiler Implementationsabhängig und könnte zu den Umgebungsvariablen zeigen.
cout ist eine mit extern deklarierte globale Variable. Dabei wird doch immer gesagt, man solle so etwas nicht tun. Neben cout gibt es noch wcout, cerr, wcerr, clog, wclor, cin, wcin. Das w steht für wide character.
Die Funktion endl bewirkt nicht nur ein Zeilenumbruch, sondern auch ein flush.
Als einzige Funktion darf main kein return enthalten. Ist es nicht explicit enthalten, wird implicit return 0 benutzt.

[1] https://gcc.gnu.org/wiki/cxx-modules

20.08.2017

C++ für Anfänger&Profis - Hello World!

Filed under: Allgemein — Tags: — Thomas @ 07:08

Das traditionell erstes Programm in einer neuen Programmiersprache gibt den Satz "Hello World!" am Bildschirm aus. Daran wollen wir uns halten. Ohne Umschweife hier der Code. Eintippen, compilieren, starten und freuen.

#include <iostream>
#include <vector>

using namespace std;

int main() {
    cout << "Hello World!" << endl;
}

Da es das erste Beispiel ist, werde ich hier jede Zeile Code und jedes kryptische Zeichen erklären.
In den ersten beiden Zeilen stehen #include Anweisungen. Diesen binden die Dateien iostream und vector von C++ ein. Die Datei iostream bietet Funktionen für die Textausgabe an, während vector einen Datencontainer anbietet. Dieser wird im nächsten Beispiel benötigt.

Zeile 4 sagt aus, dass nun der Namensraum std genutzt werden soll. Alle C++ Funktionen liegen in diesem Namensraum. Das dient zur Vermeidung von Namenskonflikten, wenn später andere Frameworks genutzt werden sollen, die gleiche Funktionsnamen wie in C++ anbieten.

In Zeile 6 geht nun das Hauptprogramm los, das immer eine Funktion namens main ist. Alle Codezeilen, die in dem Paar geschweifter Klammern zwischen Zeile 6 und 8 stehen, gehören zur main Funktion und werden ausgeführt. Die Zeile 7 wird mit vier Leerzeichen eingerückt um dem Leser deutlich zu machen, dass sie im Hauptprogramm stehen. Für den Compiler haben diese Leerzeichen keine Bedeutung.

Zeile 7 ist das Kernstück vom Programm, welche die eigentliche Textausgabe macht. Die Variable cout wird von C++ bereit gestellt und soll sozusagen den Bildschirm repräsentieren, auf welcher der Text erscheinen soll. Die beiden nachfolgenden spitzen Klammern << sind Stream Operatoren. Mit ihnen schiebt man sozusagen den Text auf den Bildschirm. Gleich darauf folgt der eigentliche Text "Hello World!" in doppelten Hochkomma eingeschlossen. Jeder Text der im Programm so verarbeitet werden soll, muss mit doppelten Hochkomma eingeschlossen sein. Damit wird dieser Text zu einem string, der vom Computer verarbeitet werden kann.

Das Ende vom Zeile 7 besteht aus einem zweiten Stream Operator und der Funktion endl, welche einfach ein Zeilenumbruch einfügt.

Beachte wann ein Semikolon gesetzt wird. Einmal bei der using Anweisung und einmal bei cout. Nicht benötigt wird das Semikolon bei der #include Anweisung und bei der öffnenden und schließenden geschweiften Klammern der main Funktion.

Gratulation, dein erstes C++ Programm!

C++ für Anfänger&Profis - Grundsätzliches

Filed under: Allgemein — Tags: — Thomas @ 07:08

Ein paar grundsätzliche Worte über die Sprache C++. Anders als z.B. Python oder PHP ist C++ eine statisch, stark typisierte Sprache.
Das heißt, dass zum Zeitpunkt, wenn der Compiler den Code compiliert, jede Variable einen Type haben muss. Damit ist es möglich Fehler schon während der Entwicklung zu entdecken, bevor das lauffähige Programm existiert. Und nicht erst Jahre später, oder sogar beim Endanwender. Weiterhin eröffnet diese Typisierung viele Optimierungsmöglichkeiten um den beachtlichen Geschwindigkeitsvorteil von C++ nutzen zu können.
So ist es z.B. nicht möglich, einer Variablen, welche eine ganze Zahl darstellen soll, einen Text zuzuweisen. Dafür ist es auf der anderen Seite aber auch nicht direkt möglich, einer Variablen, welche ein Text darstellen soll, eine ganze Zahl zuzuweisen. Diese Absicht muss im Code durch eine zusätzliche Funktion dargestellt werden.
Dadurch entstehen bei der Typ Prüfung während des Compiliervorgangs keine Fehler. Die Intention des Programmierers wird deutlich und der Code bleibt sauber.

So gut wie jede Zeile in C++ wird mit einem Semikolon abgeschlossen. Das bereitet den meisten Anfänger Probleme, was ich aber gar nicht nachvollziehen kann. So wird in der deutschen Sprache jeder Satz mit einem Satzzeichen beendet PUNKT. Analog dazu wird in C++ eine Zeile Code mit einem Semikolon beendet;

Der Compiler an sich ist neben der Entwicklungsumgebung das wichtigste Programm. Es muss nicht nur der komplette Code analysiert und in ein lauffähiges Programm umgewandelt werden, es müssen auch Programmierfehler behandelt werden. Leider ist die Compilerausgabe in dieser Hinsicht sehr kryptisch. Aus diesem Grund gibt es im Anhang eine Liste aller gängigen Fehlermeldungen des GNU C++ Compilers, ihre Bedeutung, Code Beispiele wie sie entstanden sind und wie man sie korrigiert.
Dies sollte man nicht als Schwäche, sondern als Stärke sehen. Jede Fehlermeldung gibt, einmal entschlüsselt, klare Angaben dazu was, wo schief lief. Und oft sogar Hilfestellung, wie es richtig wäre.

Weiterhin kommen viele mit den eingesetzten Klammern nicht zurecht. Es werden die drei unterschiedlichen Klammern () [] {} und die spitzen Pfeile < > überall im Code verwendet. Jede hat ihre klar definierte Bedeutung. So eröffnen die geschweiften Klammern {} einen neuen Codeabschnitt. Die runden Klammern () werden für den Aufruf einer Funktion benutzt, mit denen es möglich ist weitere Laufzeit Variablen zu übergeben. Mit einem Paar der spitzen Pfeile < > ist es möglich Compilezeit Variablen zu übergeben. Wohingegen die eckigen [] Klammern den Zugriff auf einem Datencontainer erlauben.

C++ zählt von 0 ab. Das erste Element in einer Liste liegt nicht an Position 1, sondern an Position 0. Für die meisten wird das die unangenehmste Umgewöhnung sein. Der Grund steckt tief in dem Hardware Aufbau eines Computers. Und auch ältere Sprachen wie Assembler haben diese Eigenschaft. Kurz gesagt speichern alle Datencontainer die Adresse ihres ersten Elements ab. Und da das erste Element schon an dieser Adresse liegt, würde das addieren einer 1 auf diese Adresse auf das zweite Element zeigen. Das addieren einer 0 hingegen ändert die Adresse nicht. Daher fängt C++ bei 0 an zu zählen.

Es gibt im Prinzip drei fundamentale Datentypen. Einen für ganze Zahlen int, einen für Gleitkommazahlen double, und einen für Text string. Alle andere Datentypen können aus diesem zusammengestellt werden.

C++ für Anfänger&Profis - Qt Creator

Filed under: Allgemein — Tags: — Thomas @ 06:08

Bilder von download, installation, und erstes Projekt Hello world

C++ für Anfänger&Profis - Die Entwicklungsumgebung

Filed under: Allgemein — Tags: — Thomas @ 06:08

Die eingesetzte Entwicklungsumgebung hat für mich einen großen Stellenwert beim Programmieren. Sie muss einfach zu installieren sein und einen Compiler mitbringen. Es braucht auf alle Fälle eine gute und schnelle Autovervollständigung, Syntaxhervorhebung, Fehlererkennung und Vorschläge zur Korrektur des Codes, automatische Einrückung, sowie Projekt Management.
Die Entwicklungsumgebung muss portabel für alle gängigen Computersysteme existieren und ab und zu auch mal ein Update bekommen.

Was ich nicht möchte ist, wenn die Leute anfangen im Texteditor zu programmieren. Damit legt man sich nur selbst Steine in den Weg. Die Syntax von C++ ist zugegebenermaßen schwerer als bei anderen Sprachen, aber aus diesem Grund wurden ja Entwicklungsumgebungen erfunden.

Empfehlen kann ich die Entwicklungsumgebung Qt Creator von The Qt Company welcher als kostenloser Download zur Verfügung gestellt wird. Neben einer Open Source Möglichkeit wird auch eine kommerzielle Version angeboten.

[1] https://www.qt.io/download-open-source/

C++ für Anfänger&Profis - Vorwort

Filed under: Allgemein — Tags: — Thomas @ 05:08

Es gibt so viele Bücher über C++, aber 99% davon lehren nur schlechtes C. Der Unterschied zwischen diesen beiden Sprache ist fundamental, aber von dem Laien nicht ersichtlich.
Die wenigen Bücher die sie wirklich mit C++ beschäftigen, und darunter die, welche modernes C++ behandeln, wie C++11, C++14 und C++17, die sind oft von dem Machern der Sprache geschrieben und für den Anfänger nicht verständlich. Ja, meinst kann der normale Programmierer damit auch nichts anfangen.

Aus diesem Grund versuche ich in dieser Reihe C++ für Anfänger&Profis zu zeigen, dass C++ eben keine steile Lernkurve hat und für die normalen Sachen genauso einfach zu bedienen ist, wie andere moderne Sprachen. Sollte aber fanzy C++ Performance und Techniken gefordert sein, so ist es jederzeit möglich das einzubauen. Aus diesem Grund werde ich die Beispiele jeweils zwei mal behandeln. Einmal mit Code der auf Verständlichkeit ausgelegt ist. Und einmal mit Code der aus Performance kritischer Sicht geschrieben ist.

Da mein Spezialgebiet Algorithmen und Datenstrukturen ist, werde ich die Beispiele entsprechend ausrichten.

« Newer PostsOlder Posts »

Powered by WordPress