Dipl.-Ing. Martin Weitzel, 64380 Roßdorf bei Darmstadt

ABB2020-02

EC++FOR ABB Minden (zusätzliche Beispiele, Info-Graphiken und optionale Übungen)

Kapitel: =1= =2= =3= =4= =5= =6= =7= =8= =9= =10= =11= =12= =13= =14=


Zu Kapitel 1: Namespaces


Zu Kapitel 2: Mehrfachvererbung


Zu Kapitel 3: Inheritance (incl. Multiple Inheritance / Interfaces)


Zu Kapilel 4: Exceptions

Optionale Übung:

Bauen Sie in dieses Beispiel zu Exceptions schrittweise folgende Änderungen ein:

  • Vereinfachen Sie zunächst das Werfen von Exceptions indem Sie keine Klassen benutzen sondern
    • z.B. den Typ `int` oder 
    • auch einen `enum`-Typ
    • Entfernen Sie dabei zum Testen dieses Codes den Aufruf des `T`-Makros und verwenden Sie an dieser Stelle direkt die `try`-`catch`-Blöcke:
      ```
      try {
          b.put(1001); // <--- sixth `put` (that should cause an overflow)
          std::cout << "how that? (expected that to throw)" << std::endl;
      }
      catch (int x) {
         std::cout << "!!caught error #" << x << std::endl;
      }
      ```
  • Verwenden Sie anschließend wieder Klassen um Exceptions anzuzeigen (wie im Originalbeispiel):
    • Leiten Sie `WillOverflow` und `WillUnderflow` nun aber von einer Klasse `RingBuffer_Exception` ab.
    • Passen Sie den Test-Code an die erwarteten Exceptions an. (Bleiben Sie dabei, die erforderlich `try` - `catch`-Blöcke – so weit noch nötig – direkt in die Tests zu schreiben und nicht mittels des Makros `T` zu verwenden.)

LÖSUNG: https://godbolt.org/z/MSfzys

  • OPTIONAL: Demonstrieren Sie, dass …
    • … sich die tatsächlichen Exceptions nicht nur über ihre jeweilige Klasse abfangen lassen, sondern auch über die gemeinsame Basisklasse
    • … bei mehreren `catch`-Blöcken der spezifischer (abgeleitete Klasse) vor dem allgemeineren (`RingBuffer_Exception`) erfolgen muss …
    • … weil bei umgekehrter Reihenfolge der `catch`-Blöcke (Basisklasse zuerst) diese Vorrang hat.
  • OPTIONAL: Die Basisklasse `RingBuffer_Exception` können Sie natürlich auch innerhalb der Klasse `RingBuffer_int_5` definieren, z.B. als Klasse `ExceptionBase` (was ihren Zweck als Basisklasse unterstreicht) …
    ```
    class RingBuffer_int_5 {

    public:
              class ExceptionBase { /*no content so far*/ };
              class WillOverflow : public ExceptionBase  { /*nothing added*/ };
              class WillUnderflow : public ExceptionBase  { /*nothing added*/ };

    };
    ```
    oder innerhalb nur deklarieren und außerhalb definieren:
    ```
    class RingBuffer_int_5 {

    public:
              class ExceptionBase;
              class WillOverflow;
              class WillUnderflow;

    };

    class RingBuffer_int_5::ExceptionBase { /*no content so far*/ };
    class RingBuffer_int_5::WillOverflow
            : public RingBuffer_int_5::ExceptionBase { /*nothing added*/ };
    class RingBuffer_int_5::WillUnderflow
            : public RingBuffer_int_5::ExceptionBase { /*nothing added*/ };

    ```
    • Passen Sie die Tests den neuen Erfordernissen an.

LÖSUNG: https://godbolt.org/z/Vyhc7A
und https://godbolt.org/z/XTvCdH

  • Leiten Sie schließlich die Klasse `ExceptionBase` von einer Klasse aus der Standard-Exception-Hierarchie ab (Vorschlag: `std::runtime_error`) und geben Sie in deren Konstruktor einen Text an, welcher diese Klasse sinnvoll beschreibt:
    • Hinweis: wenn dieser String in den beiden tatsächlich geworfenen Klassen unterschiedlich sein soll – was sinnvoll wäre(!) – muss der Konstruktor der Klasse `ExceptionBase` ein `const char*`-Argument haben, welches von in den Konstruktoren der endgültigen Klassen (`WillOverflow` und `WillUnderflow`) angegeben und von `ExceptionBase` an `std::runtime_error` "durchgereicht" wird.
    • OPTIONAL: Verwenden Sie in den abgeleiteten Klassen statt eigener Konstruktoren die mit C++11 eingeführte "Konstruktor-Vererbung".

LÖSUNG: https://godbolt.org/z/Pc_ohy


Zu Kapitel 5: Dynamic Memory

  • InfoGraphic: SmartPointers
  • Einführung zu "copyable" vs. "move-only": https://godbolt.org/z/zujrO8
  • OPTIONAL: C++98: `std::auto_ptr` (in C++11 "deprecated", in C++17 abgeschafft)
  • OPTIONAL: Überladungs-Demo:
    • Referenz vs. C++11-Rvalue-Referenz 
    • Klassische Referenz vs. `const`-Referenz
    • Typischer Use-case: "copyable" vs. "move-only"

Optionale Übung:

Schauen Sie sich dieses Beispiel an und demonstrieren Sie (ggf. durch zusätzlich eingefügte Tests):

  • `NamedValue_int` lässt sich verwenden wie ein `int`
    (bedingt durch die automatische Typ-Umwandlung in eine `int`-Referenz):
    • Man kann damit "rechnen", neue Werte zuweisen usw. …
    • … lediglich bei der Ausgabe, wird dem aktuellen Wert der Konstruktor angegebenen Bezeichner vorangestellt.
  • `Named_value_int` Objekte sind nicht "copyable" aber "movable".

Bitte überlegen Sie zuerst selbst, wie einen Test auf "move only"-Typ aussehen könnte!

Wenn Sie keine eigene Idee haben, hier der Test-Code, den Sie verwenden könnten:

```
auto foo() {
          return NamedValue_int{"returned-from-foo", -1};
}
void move_NamedValue_int_demo() {
          NamedValue_int x{ "local in-main", 123 }; // OK
          std::cout << x << std::endl;
          // NamedValue_int y{ y }; // <---- ERROR (copying not supported!)
          NamedValue_int y{ foo() }; // OK (move-operations defined)
          std::cout << y << std::endl;
          NamedValue_int z{ std::move(x) }; // <----- OK … BUT: from here on
          std::cout << y << std::endl; // `x` is only destructable now
          // std::cout << x << std::endl; // <--- and SHOULD NOT BE further used!
          // x = z; // <-------------------- again: NO `copy` assignment ...
          x = std::move(z); // but OK to do `move` assignment
          std::cout << x << std::endl;
          // std::cout << z << std::endl; // <--- should not be used any longer
}
```
(Und hier ist das entsprechend ergänzte Gesamtbeispiel.)

Optionale Übung zu `std::unique_ptr`

  • Ersetzen Sie in der Klasse `NamedValue_int` …
    • … das Member-Datenelement `const char* name;` durch
    • den Smart-Pointer `std::unique_ptr<char[]> name;`
  • Zeigen Sie, dass damit folgendes ersatzlos entfallen kann (oer sogar muss):
    • der Destruktor, welcher `name` freigibt;
    • der (implementierte) "move`-Cconstructor
    • die (implementierte) "move"-Zuweisung
      • nach wie vor ist die Klasse aber "movable"
    • der (gesperrte) "copy`-Cconstructor
    • die (gesperrte) "copy"-Zuweisung
      • nach wie vor ist die Klasse aber nicht "copyable".

LÖSUNG: https://godbolt.org/z/XejqNB

  • Fügen Sie für die nachfolgend genannten Operationen die passende `=delete`- oder `=default`-Definition ein:
    • Konstruktor ohne Argumente (aka. Default Constructor);
    • Kopier-Konstruktor
    • Move-Construktor
    • Kopier-Zuweisung
    • Move-Zuweisung
    • Destruktor

​​​LÖSUNG: https://godbolt.org/z/ji6qwB

  • OPTIONAL: Was ist noch hinzuzufügen, wenn Sie …
    • Kopieren (im Sinne eines "Klonens") unterstützden möchten?
    • ODER das Kopieren mit Namens-Anpassung?
      – "abc" ===> "copy-of abc"  
      – "copy-of abc" ===> "copy-of copy-of abc"

LÖSUNG: https://godbolt.org/z/PXju7z

 


Zu Kapitel 6: State Machine

  • Eweiterung der folgenden beiden Statemachines Decomment_FSM
  • um die Zustände `ChrLiteral` und `QChrLiteral` (prinzpiell ganz ähnlich zu den bereits vorhandenen Zuständen `QStrLiteral` und `QStrLiteral`)
    • `ChrLiteral` wird durch ein einfaches Anführungszeichen (') betreten und verlassen;
    • `QChrLiteral` wird (von `ChrLiteral` aus) durch einen Gegenschrägstrich betreten und durch jedes beliebiges Folgezeichen wieder verlassen.

 


Zu Kapitel 9: Dynamischer Polymorphismus (RTTI)

 


Zu Kapitel 10: Templates


Vergleich: Flexible Erweiterungspunkte mit virtuellen Funktionen oder Template:


Optionale Übung:

Wandeln Sie in diesem Beispiel die Klasse `RingBuffer_int_5` schrittweise um in eine Template. (Nach jedem der Haupt-Schritte sollten Sie über eine kompilierbare Lösung verfügen!)

  • Führen Sie für den generischen Typ das Symbol `T` und für die Größe das Symbol `N` ein, aber …
    • machen Sie die Klasse `RingBuffer_int_5` noch nicht zur Template!
    • Definieren Sie die beiden Symbole `T` und `N` vielmehr über eine globale Konstante und eine globale Typdefinition:

```
// typedef int T;    // <---- klassische C-Syntax ODER
using T = int; // <----- wahlweise neue C++11-Syntax
const std::size_t N = 6;

LÖSUNG: https://godbolt.org/z/n9VAYU

  • ERST Wenn das Programm damit fehlerfrei kompiliert …
    • gehen Sie mit "Suchen und Ersetzen" durch den Code und nehmen Sie folgende Umwandlungen vor
      RingBuffer_int_5 ===> RingBuffer<T, N>
  • ausgenommen
    • im Namen der Klasse selbst (also direkt dort wo sie definiert wird);
    • wenn der Klassenname als Konstruktor oder Destruktor auftrifft;
    • nirgendwo in der Funktion `basic_RungBuffer_demo` (dazu später).
  • In dieser Form wird der Code (noch) nicht kompilieren!
    • Fügen Sie nun vor der Definition der Klasse selbst
      und vor der Definition jeder Member-Funktion außerhalb der Klasse folgende Zeile ein

```
template <typename T, std::size_t N>
```
(Damit erklären Sie dem Compiler, dass im direct nachfolgenden Code { … }-Block `T` für einen beliebigen Typnamen und `N` für einen für eine beliebige konstante vom Typ `std::size_t` steht. Die beiden Zeilen, mit der Sie die Bezeichner `T` und `N` dem Compiler vorher bekannt gemacht hatten, können Sie nun löschen.)

  • In dieser Form wird wird der Code (noch) nicht kompilieren!
    • Fügen Sie schließlich in die Funktion `basic_RingBuffer_demo` folgendes ein:

```
// typedef RingBuffer<int, 5> RingBuffer_int_5;  // <---- klassische C-Syntax ODER
using RingBuffer_int_5 = RingBuffer<int, 5>; // <--- alternativ neue C++11-Syntax

```
(Alternativ können Sie auch jedes Vorkommen des Names `RingBuffer_int_5` in der Funktion `basic_RingBuffer_demo` ersetzen durdh `RingBuffer<int, 5>`, also "fast" wir zuvor geschehen aber dieses Mal mit konkreten Typen, nicht allgemeinen Symbolen. Die Verwendung einer Typdefinition wie oben vorgeschlagen ist jedoch einfacher und sicherer: sie ist schneller erledigt und man kann nichts übersehen.)

  • Jetzt sollte der Code wieder kompilieren!
    • Testen Sie nun noch mehrere Varianten der `RingBuffer`-Template.
    • Sie können dazu einfach den Code von `basic_RingBuffer_demo` kopieren und die notwendigen Änderungen vornehmen (wobei Sie daran denken sollten, dass dies weitere Anpassungen nach sich zieht).

LÖSUNG: https://godbolt.org/z/WrrkiE

  • Wenn Sie den "payload"-Datentyp ändern, müssen Sie auch die mit `put` abgespeicherten und mit `get` ausgelesenen Werte-Typen entsprechend anpassen.
  • Wenn Sie die Maximalzahl der Elemente im Puffer ändern, müssen Sie auch die Anzahl der `put` und/oder `get`-Aufrufe (bis der Puffer voll oder leer ist) entsprechend anpassen.

LÖSUNG: https://godbolt.org/z/hfNZrv

Zu Kapitel 11: STL

Zu Kapitel 12: Programmierung von Parallelen Systemen

Zu Kapitel 13: Hardware-Abstraktion und Treiber mit C++

Zu Kapitel 14: Übungen