C++Guns – RoboBlog blogging the bot

05.03.2017

TODO C++ Guns - Vererbung und Datenstrukturen und Performance

Filed under: Allgemein — Tags: — Thomas @ 20:03

Ich denke, Vererbung und Datenstrukturen sind nicht gut für die Performance.
Mir ist auch noch kein so wirklicher Anwendungsfall in der Praxis begegnet. Heute habe ich mal was probiert.
Ein Stück Kanal, welches in der Erde liegt und Abwasser durch schwimmt, nenne ich von nun an eine Haltung. Eine Haltung hat mehrere Attribute, die für eine Kanalnetzberechnung von Bedeutung sind. Z.B. Nummer, Länge, Breite, Profil, Rauigkeitswert, Zulauf und Ablauf Haltungen. Diese Variablen müssen für die Performance nah im RAM beieinander liegen. Es sollten also keine Variablen im Datentype existieren, die nicht für die Berechnugn relevant sind. Aber dennoch hat eine Variable weitere Attribute, wie z.B. den Straßennamen, Haltungsbezeichner, Schachtbezeichner und viele mehr.
Es ergeben sich also zwei getrennte Datentypen die beide eine Haltung darstellen. Das ist verwirrend. Ich kann natürlich einen normalen Haltungsdatentyp erstellen mit allen Attribute und einen Performance Haltungsdatentyp mit nur wenigen wichtigen Attribute. Aber das ist auch eine Art doppelter Code.
Also versuchen wir es mal mit Vererbung. Der Datentyp Haltung erbt vom Datentype HaltungPerformance.


struct HaltungPerformance {
    int NR = 0;
    double laenge = 0;
    int B = 0;
    int profilKennziffer = 0;
    int ZU[3];
    int AB[2];
    int KB = 1;
};

struct Haltung : public HaltungPerformance
{
    string strassenNamen;
    string bezeichner;
    string schachtBezOben, schachtBezUnten;
};

class DataBase {
public:
private:
  std::vector<Haltung> haltungen;
}

Das Befüllen der Datenstruktur und das normale Arbeiten mit Haltungen ist einfach. Das ein Teil der Haltung in einem zweite Klasse ausgelagert ist, ist hier transparent und für den Programmierer nicht sichtbar.

Aber wie kann mit der performanten Haltungs- Klasse die Berechnung durchgeführt werden? Eine Möglichkeit ist es, eine Kopie der HaltungPerformance Daten zu machen und in ein extra vector zu speichern.


  std::vector<HaltungPerformance> haltungenRechnen(begin(haltungen), end(haltungen));

Nunja, vorher hatten wir doppelten Code, nun haben wir doppelte Daten. Gibt es noch einen anderen Weg?
Versuchen wir es mal nicht mit Vererbung, sondern mit Composition? (Sagt man das so?)

03.03.2017

C++ Guns - Fun with enable_if

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

Schonmal std::enable_if benutzt? Ich nicht. Man kann damit Funktionen deaktivieren... Irgendwie... SFINAE... Gesundheit!
Die Beispiele dazu sind alles nur Kraut und Rüben. Vor lauter Template Scheiße sieht man den Kern der Aussage nicht.
Also, hier mein eigenen Testfall:


// Template Parameter N wird einfach an die Funktionen weitergegeben
template<int N>
class Testclass {
public:

    // Um enable_if und SFINAE zu nutzen, muss die Funktion ein Template sein.
    // Dummerweise muss der Template Parameter afaik auch in enable_if genutzt werden.
    template<int U=N>
    enable_if_t< U>=1 , int>
    eins() {
        return 1;
    }

    // Wo steckt der Rueckgabetyp? Im zweiten enable_if Template Paramter. Strange...
    template<int U=N>
    enable_if_t< U>=2 , int>
    zwei() {
        return 2;
    }

    // Dabei darauf achten, dass die beiden dreioverload() Funktionen auch unterschiedliche Template Typen
    // haben. Sonst ist, wie beim normalen function overload, kein ueberladen moeglich. Wo ist der Unterschied
    // in den Typen? N ist gleich. U ist gleich. int ist gleich. Nur der erste Parameter von enable_if evaluiert
    // einmal zu true und einmal zu false. Das sind zwei unterschiedliche Typen!
    template<int U=N>
    enable_if_t< U>=3 , int>
    dreioverload() {
        return 3;
    }

    template<int U=N>
    enable_if_t< !(U>=3) , int>
    dreioverload() {
        return -1;
    }
};

    cout << Testclass<1>().eins() << "\n";
    cout << Testclass<2>().eins() << "\n";
    cout << Testclass<2>().zwei() << "\n";
    cout << Testclass<2>().dreioverload() << "\n"; // Print -1

Mal abwarten ob so etwas im normalen Code sinnvoll einsetzbar ist.

01.03.2017

todo: owning and non owning

Filed under: Allgemein — Tags: — Thomas @ 19:03

Jeder Funktionsparemter der (const)(Referenz)(Pointer) ist, ist non-owning

C++ Guns - Point3D ist KEINE Erweiterung von Point2D!

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

Point3D ist KEINE Erweiterung von Point2D!
Sondern Point2D ist eine Reduzierung von Point3D. WTF? Ja ist so :P

Beweis:


struct Point2D {
    double x,y;
};

struct Point3D : public Point2D {
    double z;
};

static_assert(std::is_standard_layout<Point3D>::value);
main.cpp:14:1: error: static assertion failed
static_assert(std::is_standard_layout<Point3D>::value);

In der C++ Sprache ist alles, was berechnet werden kann, ausdrückbar. Wird dabei das Standard Layout nicht eingehalten, ist definitiv der falsche Weg eingeschlagen worden.

Der zweite Tipp ist, dass ohne Standardlayout die Typen auch nicht POD sind. Und man kann wohl getrost annehmen, wenn ein double POD ist. Dann muss auch ein Type mit 2 oder 3 double POD sein!

Lösung:
Point2D ist auch im Point3D enthalten. Es wird halt nicht x,y,z beachtet, sondern nur x,y.
Wenn PointND als Array implementiert wird, ist das nichts anderes als array_view (string_view).
Sprich, wird der Datensatz als Point3D gespeichert (owning), sind Point2D und Point1D non-owning.

Beispiel:


template<size_t N>
struct PointNDRef : public array_view<double, N>
{
    using array_view<double,N>::array_view;
};

template<size_t N>
struct PointND : public std::array<double,N>
{
    operator PointNDRef<2>() {
        return this->data();
    }

    operator PointNDRef<1>() {
        return this->data();
    }
};

static_assert(std::is_standard_layout<PointND>::value);

using Point3D = PointND<3>;
using Point2DRef = PointNDRef<2>;

void func(const Point2DRef p) {
    p[0];
    get<0>(p);
}

int main() {
    Point3D p;
    func(p);
    return 0;
}

Die Implementation von array_view kommt dann irgendwann die Wochen.

28.02.2017

C++ Guns - type traits Beispiel

Filed under: Allgemein — Tags: — Thomas @ 19:02

Hm templates sind zwar toll und so, aber ein template Typ kann ALLES sein. Da ist Polen offen, wie mein Chef sagt. Ist schon komisch. Programmiert man feste Typen ein, sind sie nicht variabel genug. Nimmt man templates, sind sie zu variabel. Im Endeffekt kommt es doch aber nur auf die Eigenschaft des Typs an. Die meisten Algorithmen die integer nutzen, scheren sich nicht darum ob das nun ein short oder long Type ist. Einfache oder doppelte Zahlengenauigkeit - egal. Ein 2D Linien Type von Qt, Boost oder meinen eigenen - egal. Wichtig ist doch nur, was der Type kann, und nicht wie er im Detail aufgebaut ist.
Also muss für jeden template Parameter auch noch deren Eigenschaften im Programm mitgeteilt werden. type_traits macht genau das.
Hier ein kleines Beispiel von gerade eben.


template<class T>
struct is_line : std::false_type {
};

template<>
struct is_line<QLineF> : std::true_type {
};

template<class Container>
void func(const Container& lines) {
  static_assert(is_line<typename Container::value_type>::value, "Expect a geometric line type for Container::value_type");
  for(const auto& line : lines) {
    ...
  }
}

Und so schaut eine mögliche Fehlermeldung dann aus, wenn die Funktion mit nicht line-Typen aufgerufen wird.

error: static assertion failed: Expect a geometric line type for Container::value_type

Hint: Das keyword "typename" wird gebraucht, weil der Type "Container" ein template Type und gleichzeitig "Container::value_type>" auch wieder von einem template Type kommt.

27.02.2017

Protected: Was ich bin

Filed under: Allgemein — Tags: — Thomas @ 19:02

This content is password-protected. To view it, please enter the password below.

C++ Guns - Auto Variables - Gute Beispiele

Filed under: Allgemein — Tags: — Thomas @ 11:02

GotW 93 von Herb Sutter finde ich sehr gut. Es wird eine Reihe von Möglichkeiten für "auto" anhand von Beispielen gezeigt und erläutert. Das erleichtert das Programmieren wirklich sehr. Schon 1983 wurde "auto" von Bjarne Stroustrup in C++ implementiert. Aber dank dem schon damals schrottigem C musste er es wieder raus nehmen. Na, ich bin sehr froh post C++11 zu leben.

GotW 93

Es gibt auch eine Situation, in welche "auto" auf den ersten Blick ein falschen Typ ableitet. Aber bei genauerer Betrachtung ist es das alte Problem von unterschiedlichen integer Typen. Also unsigned long int + signed int = unsigned long int. Der unsigned long int Datentyp ist "stärker" als signed int. Diese Definition geht auf die Anfängen von C zurück. Und es gibt keine Lösung die alle Fälle abdeckt. Es können weder signed Werte in unsigned long dargestellt werden, noch passen unsignled long in signed int rein. Aber jedesmal auf under/overflow zu prüfen ist einfach nicht effizient genug. Auch "auto" wird dieses Problem nicht lösen. Aber es eliminiert die Folgefehler.

Wie erwähnt, ist der Ergebnistyp bei der Subtraktion von unsigned long und signed int wieder ein unsigned long, obwohl der Programmierer ein signed int als Ergebnis haben wollte. Ohne die Benutzung von "auto" ist das auch einfach zu erreichen. Aber es ist nicht ersichtlich, dass im Code ein versteckter Fehler lauert:


unsigned long x    = 42;
signed short  y    = 43;
int diff = x - y;

Also das ist das reinste Kudelmuddel. unsigned long und signed short gehn rein, und signed int kommt raus. Jetzt kann das Ergebnis wegen unsigned long aber größer sein, als ein int speichern kann. Und mit der Benutzung von "auto" ist der Ergebnistyp wieder unsigned long und kann damit keine negativen Werte speichen, obwohl das offensichtlich gewollt ist.

Als Ergebnistyp würde signed long am besten passen. Aber erklär das mal den Programmierer, und dokumentiere diesen Pitfall an 1000 Stellen im Code, so dass es auch in 30 Jahren noch nachvollziehbar ist: no way!
Andrei Alexandrescu Idee finde ich hier sehr schön. Wir lassen natürlich den Compiler die Arbeit für uns machen! Der Compiler hat schon die Information, dass der Ergebnistyp ein long sein muss. Das ein signed Typ gewollt ist, sagt der Programmieren. In Kombination mit C++11 std::make_signed, welches für jeden gegebenen integral Typ, einen signed integral Typ ausspuckt, lassen sich schön zwei Hilfsfunktionen erstellen.


// C++14 version, option 1
template auto as_signed  (T t){ return make_signed_t  (t); }
template auto as_unsigned(T t){ return make_unsigned_t(t); }

auto diff = as_signed(x - y);

Durch diesen Einzeiler wird der bestmögliche Typ abgeleitet und gleichzeitig spricht der Code, dass hier was besonderes mit der Arithmetik passiert.

/// \todo Beispiel Code der zeigt was mit const und referenzen passiert.

26.02.2017

Protected: Graph Cycle Schlange die sich selbst beisse

Filed under: Allgemein — Tags: — Thomas @ 20:02

This content is password-protected. To view it, please enter the password below.

16.02.2017

C++ Guns - C++ ist kein object oriented sprache

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

Wie Arne Mertz schon in seinem Beitrag c-is-not-an-object-oriented-language/ sage, ist C++ keine Objekt orientierte Sprache. Ich möchte das aus meiner Sicht einmal verdeutlichen.
Eine Definition ob eine Sprache eine Objekt orientiere Sprache ist, wenn damit Objekt orientiert programmieren kann. Doch was ist ein Objekt?

Aus Objekt orientierter Sich ist ein Objekt eine Klasse, mit Funktionen und Attributen. Dabei kann sich das Verhalten des Objekts, also seine Funktionen, zur Laufzeit mittels Vererbung ändern (c++: virtual). Doch wann hört ein Objekt auf, ein Objekt zu sein? Das ändern des Verhaltens zur Laufzeit ist IMO keine Voraussetzung. Auch muss ein Objekt IMO keine Funktionen besitzen oder von anderen Klassen erben. Ein Objekt mit nur Attributen hat auch oft eine Entsprechung mit einem realen Objekt in der Welt. Zum Beispiel kann jeder Gegenstand mit Koordinaten xyz versehen werden. Dieses Koordinaten-Objekt selbst ist aber wieder aus drei einzelnen Objekten zusammengesetzt. Eben die Variablen, welche die x, y und z Koordinate speichern. Aus meiner Sicht ist schon eine einfache Variable, welche sogar direkt von einer spezifischen Hardware abhängig sein kann (size_t), schon ein Objekt.

So, C++ ist keine pure Objekt orientierte Sprache. Keine pure funktionale Sprache. Keine pure prozedural Sprache. Keine pure low level Sprache.
C++ ist eine multi paradigma Sprache. Und das ist auch gut so.
Für jede Aufgabe gibt es eine gute Lösung. Aber reale Welt Probleme bestehen aus vielen einzelnen Aufgaben die jede für sich eine gute Lösung besitzt. Wie kann dann überhaupt angenommen werden, dass eine Sprache, dir nur ein einzelnes, oder wenige Paradigmas unterstützt, überhaupt jemals echte Probleme gut lösen kann?

06.02.2017

C++ Guns - call by reference; return by auto

Filed under: Allgemein — Tags: — Thomas @ 16:02

Es gibt viele Arten Daten in eine Funktion hinein zu stecken und auch wieder heraus zu bekommen. Hier liste ich die Vor- und Nachteile auf.
Hinein ist einfach:

Call by reference:

Sofern es sich um stink normale Daten handelt, ist das die bevorzugte Methode. Nicht stink normale sind z.B. Funktionen/-Objekte.


void callByRef(const Point3D& point);

Eine Referenze belegt dabei genauso viel Platz wie ein Pointer, also 8byte. Hat man also nur ein int/real zu kopieren, lohnt es sich nicht. Bei so kleinen Objekten ist call by value genauso gut.

Call by value:
Bevorzugt für kleine Objekte oder wenn explizit eine Kopie gebraucht wird. Auch für Funktionsobjekte, Lambdas, Iteratoren etc. Da call by value immer eine Kopie erzeugt, sollt es nicht für große Objekte verwendet werden.


template
void callByValue(const int x, int float, Iterator iter, Function pow, Rand rand);

Call by pointer:
Nee braucht man nicht. Alle sinnvolle Fälle sind schon durch reference und value abgedeckt. Ausserdem, schonmal versucht ein const Pointer auf ein const Objekt zu machen? Hässlich.


void callByPointer(Data const * const data) {
 *data = x; // BUMM
 data++; // BUMM
}

Heraus ist schwer:
Return by reference:
So etwas macht nur Sinn, wenn die Funktion eine Memberfunktion einer Klasse ist. z.B: std::vector::at(). Das Objekt, auf das die Referenz zeigt, muss ja noch weiter existieren, nachdem die Funktion wieder verlassen wurde. Aber normale Variablen in Funktionen existieren nur bis zum Funktionsende.

Return by value:
Ich nehme an, dass ist der normale Fall. Für einfache Typen (int, real) ist das auch kein Problem. Für komplexe Datentypen mit move Konstructor ist es auch kein Problem. z.B.


auto returnByValue() { return std::vector x;}
std::vector = returnByValue();

Ansonsten kann noch die Compiler Optimierung zuschlagen und einem retten. Aber seit dem man moven kann ist das alles kein Ding mehr.

Return by auto:
Mehrere Variablen zurück geben ist kein Problem.


auto returnByAuto() { return make_pair(x, true); }
auto [x, ok] = returnByAuto();

Structured Bindings nennt sich das.

Return by pointer:
Komm, spar's dir.

« Newer PostsOlder Posts »

Powered by WordPress