C++Guns – RoboBlog

12.12.2018

C++ Guns: Rätsel 2018

Filed under: Allgemein — Tags: — Thomas @ 21:12

Betrachte ein unstruturiertes Gitternetz bestehend aus Punkte und Dreiecken. Jeder Punkt kann durchnummeriert werden. Für jeden Punkt IP bezeichnet die Variable NCONN die Anzahl der angeschlossenen Punkte pro Punkt. Und die Variable NCONE bezeichnet die angeschlossene Dreiecke pro Punkt.
Es ist leicht zu sehen, dass Punkte am Rand eine unterschiedliche Anzahl von angeschlossenen Punkte und Dreiecke haben. Wärend Punkte im Mesh gleich viele angeschlossene Punkte und Dreiecke haben.

Unstructured Grid

Folgender C++ könnte grob so ein Gitternetz beschreiben:

#include <vector>

struct Mesh {
  std::vector<int> NCONN, NCONE;

  void update_NCONN_NCONE() {
     // Genaue Impl. nicht relevant für das Rätsel
  }

  bool istRandpunkt(int IP) const {
    return NCONN[IP] != NCONE[IP];
  }
}

Vor der ersten Benutzung der Funktion isRandpunkt müssen die Variablen NCONN und NCONE gefüllt werden. Dies übernimmt die Funktion update_NCONN_NCONE. Die genaue Implementierung dieser Funktion ist für das Rätsel nicht relevant, und wird daher nicht angegeben.

Nun muss der Programmierer darauf achten, dass diese Funktion auch immer aufgerufen wird. Programmierer sind aber auch nur Menschen und machen Fehler. Sollte der Aufruf von update_NCONN_NCONE einmal vergessen werden, stürzt das Programm im besten Fall ab. Im schlechtesten Fall werden falsche Ergebnisse produziert.

Wie kann dem Programmierer geholfen werden?
Es muss auf eine Art sichergestellt werden, dass die Variablen NCONN und NCONE immer mit den aktuellen Daten gefüllt sind. Allerdings darf die Funktion update_NCONN_NCONE nicht unnötig oft aufgerufen werden, da sie unter Umständen eine lange Laufzeit zur Folge hat.

Beachtet auch, dass dieser Code zur Laufzeit in ein Programm geladen werden könnte (Library/Plugin).

Es gibt für jedes Problem viele Lösungen und auch wenige gute.

Beispiel
In der Funktion istRandpunkt bei jedem Aufruf zu testen, ob die Funktion update_NCONN_NCONE aufgerufen werden muss würde zwar sicherstellen, dass die Variablen immer aktuell sind. Allerdings ist das so keine gute Idee, da dies die Laufzeit negativ beeinflusst.

struct Mesh {
  mutable std::vector<int> NCONN, NCONE;
  mutable bool needUpdate = true;

  void update_NCONN_NCONE() {
     if(not needUpdate) return;
     needUpdate = false;
     // Genaue Impl. nicht relevant für das Rätsel
  }

  bool istRandpunkt(int IP) const {
    update_NCONN_NCONE();
    return NCONN[IP] != NCONE[IP];
  }
}

In der Implementierung zeigen sich noch weitere Hässlichkeiten. Die Funktion istRandpunkt sollte als const attributiert sein, da sie keine Daten ändert. Allerdings widerspricht das der Idee, die Variablen on-the-fly zu aktualisieren. Daher müssen sie als mutable deklariert werden.
Damit werden aber Dinge im Hintergrund getriggert, welcher der Programmierer nicht erwartet. Die Funktion istRandpunkt sagt vom Funktionsnamen nicht aus, dass noch ein Update passiert.

Lösung 1: Tags
Die Idee ist, dass die Funktion istRandPunkt nur dann aufgerufen/compiliert werden kann, wenn als Argument irgendetwas mitgegeben werden kann, was vorher nur von der Funktion update_NCONN_NCONE erstellt wurde. Beispielsweise mittels einer Tag Variable. Diese kann nur von update_NCONN_NCONE erstellt werden.

struct Mesh {
    private:
    struct Tag { };

public:
  std::vector<int> NCONN, NCONE;

  Tag update_NCONN_NCONE() {
     // Genaue Impl. nicht relevant für das Rätsel
     return Tag();
  }

  bool istRandpunkt(int IP, Tag) const {
    return NCONN[IP] != NCONE[IP];
  }
};

auto func() {
    Mesh mesh;    
    auto tag = mesh.update_NCONN_NCONE();
    return mesh.istRandpunkt(1, tag);
}

Der Nachteil ist schnell erkannt. Sobald mehr als ein Mesh existiert, kann die tag Variable mehrfach genutzt werden. Es existiert keine Bindung zu einer speziellen Instanz eines Mesh Types.

Lösung 1: Enums

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress