# Computer Science 3 Lab Exercises C++ This repo documents the transition of the third Computer Science lab exercises from Java to C++. The Course is about programming concepts rather than a specific language. Please note, that `const` has not been used everywhere possible in this codebase. To `const` and `constexpr` everything will be a good future exercise to hone programming skills. ## Changes ### Worksheet 4 #### PanelCommandTypes - When using Qt, no `ActionListener`s have to be used to register the click of a button. Rather a callback function `slot` is `connect`ed to an event `signal` of the button. So `setController()` doesn't instanciate any Listeners, but `connect`s the appropriate `slot`s to the button `signal`s. - In Qt lists can be displayed either by a simplified `QListWidget`, or via a Model/View structure. - `QListWidget` can only hold `QListWidgetItems`, - Model/View can represent any data because the `QAbstractItemModel` is implemented by the user. - For the `commandTypeList` the widget was chosen. - Because of that the subclass `ComTypeListWidgetItem` had to be created to allow the Item to remember its `CommandType`. #### PanelCommandConfig - For the Panels a `QStackedWidget`, comparable to `CardLayout` is used. - The constants for switching between panels were moved into an `enum`. Enums are implicid integers, while `enum class`es have their own type. - The config panels are passed a pointer to the `ControlUI` and remember it to later call `updateTableView()` directly. This could be changed to the signals&slots mentality of Qt. The panels would not have to know the `ControlUI` and would just emit a `signal` to initiate the update. The connection would happed in `ControlUI.setController()`, just like with the other panels. #### PanelCommandTable - `TableCommandModel` - A change in the `ControlModel` are communicated by the `TableModel`. It signals `dataChanged()` for an area of changed cells or `layoutChanged()` if row or column counts were changed. - Because of this the method `onChange()` was added to the model to be called by the panel. - `onChange()` checks the dimensions of the table, emitting `layoutChanged()` if appropriate. - `onChange()` also finds the cells that need to be updated in the View and emits `dataChanged()` for each cell - Single cells are updated rather than the whole area. When figuring out Qt Tables two similar `QModelIndex`es worked more reliably then two different ones. - Later it was noted the same signals can also be emitted from the Panel and connected manually to the [`dataChanged()` slot](https://doc.qt.io/qt-6/qabstractitemview.html#dataChanged) of the View. The control flow would then match the java version. - Maybe it would be a good idea to have the `tableModel` observe the `ControlModel` via `IControlModelListener` and add a function `listChanged()` there? - The `data()` method does not only define the text, but also other properties of each cell. These should be considered when overriding the function. An empty `QVariant()` means invalid. - `PanelCommandTable` - The `ListSelectionListener` has again been substituted with an appropriate callback function/`slot`. - Because `ControlUI` should be the central starting point for table updates, the buttons emit the signal `pleaseUpdateTable()` which is connected to the `ControlUI`. - Using regular function calls this resulted in a recursive include loop where `ControlUI` and `PanelCommandTable` depended on each other. With signals and slots neither the sender nor the receiver need to know each other, just the one who connects them does. - This would have been completely avoidable if `updateTable()` was called directly inside the panel, but well it works. #### ControlUI - `updateTableView()` and `updateConfigView()` are declared as slots such that the signals of the panels can be connected to it. - `QActions`, needed for the `QMenubar`, are not taken ownership of by the `QMenu`s they are assigned to. Because of this they need to be cleaned up manually. They also get `triggered()` rather then `clicked()`. ### Worksheet 3 #### Background - Many files were added so that all excercises could be solved, enough funtionality was introduced to allow some level of simulation. - `ComHandler` and `RoverHandler` were implemented as Singletons as they are not members of `ControlModel`. They could be implemented differently. With `getInstance()` as the interface to get a reference to the `ComHandler` instance this will not break the implementation. - `ComState`, an enumeration class, was introduced to handle the States of `ComHandler` in a simpler, more defined way. - `ObjectFileHandler` - The vectors are passed by reference. - `dynamic_pointer_cast` is used for polymorphism using `shared_ptr`. - *!Currently there is a problem because the OFH has to include e.g. `Gear.h` to instanciate commands,* *but `Gear.h` us implemented by the user.* It is fine for a file from `src/` to include from `include/`, but not the other way around. "Solved" by including with manual path `#include "../src/Gear.h"` rather than relying on CMake. #### CommandType - Yields a `std::shared_ptr<ICommand>` rather than a `Command`. - `Element` was extended with a constructor that takes an existing `shared_ptr`, rather then creating a new one. This was done because it would be unnecessary to extract the raw `Command` out of the returned `shared_ptr` to `Command`, only to pass it to `Element` where it is wrapped into a `shared_ptr` again. #### ControlModelRegistry - The registry holds `weak_ptr`s rather than `shared_ptr`s because it should not own the `listeners`. `Weak pointers` are locked and checked for validity when they are used. - Earlier `std::erase` was used to remove `listeners` from the `registry`, but this doesn't work for `weak_ptr`, so a for-loop with an `iterator` is used. - To notify the `listeners` a *range based for-loop* is used. Here one can see how code complexity can be reduced by using modern features of Cpp. #### ControlModel - The instance variable of the singleton pattern was moved to inside the `getInstance()` function reducing complexity. - To register the `ControlModel` to the `ComHandler` the raw pointer had to be used. This should be ok as the lifetime of the singleton instance should last until the program is terminated. The raw pointer was wrapped into an `optional` in `ComHandler` which might be unnecessary because it could also be `nullptr`. It is more expressive tough. - `commandPerformed()` receives the `shared_ptr ICommand` wrapped into an `optional` as well. Again not necessary, but clearly states that the `command` is `optional`. Because of the `dynamic_cast` to `Command` the `optional` is redundant. - `start()` gathers every `shared_ptr<ICommand>` saved in `CommandList` A simple `for-loop` has to be used, after writing `ControlModelRegistry` with `for-ranged` and `iterator based for-loops` this feels wrong and unsafe. Maybe a good opportunity to write an own `iterator` for `CommandList`? - The `getters` return references to private data of `ControlModel`, likely `const` should be used to protect them from the harsh world outside `ControlModel`. - Since no `Rover` could be selected `getSelectedRoverId()` returns an `optional string`. #### Test - The test was expanded also show adding `Commands` from the `CommandTypes` as well as reading from and writing to a file. - Because `ObjectFileHandler` was created, another test was added for for that. ### Worksheet 2 #### Background - `commandlib.h` was introduced as a stand in for `hsrt.mec.controldeveloper.core.com.command`. For simplicity everything is defined in one header. - Diverging from the class diagram the other interfaces dont inherit from `ICommand` as that would leed to weird inheritance loops where e.g. `Gear` inherits from `Command` which inherits from `ICommand`, but also from `IDirection` which also already inherits from `ICommand`. Maybe the class diagram could be simplified if `ICommand` would be replaced by the partially virtual `Command`. #### Commands - `Gear`, `Pause` and `Direction` use multiple inheritance to allow the use of interfaces. This is fine, but it should be good practice to inherit from one base class only and only virtual classes otherwise. #### CommandList: - `CommandList` now needs a default constructor for `Element` because `Command` is now virtual and can no longer be use for the root `Element`. - `CommandList` and `Element` use template functions to allow adding multiple types of `Command`s. This sort of is compile-time polymorphism. - There is no checking if the template got the right type though. Possibly there is a solution using `static_assert` and `std::is_...`. - For variable types templates should use `typename` rather than `class`. #### Class vs Member variables - To call a static function without an object it is necessary to use the namespace operator `MyClass::foo()` rather than `MyClass.foo()`. #### Doxygen - see Trivia ### Worksheet 1 #### Element: - In Element every mention of Element is replaced with `std::shared_ptr<Element>`. - Command cmd becomes `std::shared_ptr<Command>`. - In the constructor `Element(Command cmd)` uses `std::make_shared<Command>(cmd)` to produce a `std::shared_ptr`. - The destructor `~Element()` may be configured to log destruction of removed Elements. #### CommandList: - the `Element root` now is a `shared_ptr<Element>` and is initialized inline with `std::make_shared<Element>(Command("root"))`. This is necessary because a shared pointer is empty upon construction. - All member functions previously returning `Command`s now return `std::make_shared<Command>`. This is to allow for returning `nullptr` if something went wrong. - `getElement()` now returns `std::shared_ptr<Element>`. - `getPos()` now returns `std::make_shared<Command>`. - Where appropriate the funtion parameter `int` has been replaced with `unsigned int` to prohibit passing negative values. - In `add(Command cmd)` `std::make_shared<Element>(cmd)` is used to create a new `Element`. Here is easily visible how std::make_shared<T>() is the C++ equivalent to Java's `new` operator. - The `std::bad_alloc`-Exception possibly thrown by `std::make_shared<Element>()` is not catched in `add()`, to not introduce `try{} catch{}` too early. Thus add() wont return `nullptr`, but crash the program if an error occures. ## Trivia ### inline keyword Although `inline` can be used to influence performance, but this is [not how it should be used](https://www.youtube.com/watch?v=GldFtXZkgYo). Much rather it can be used to deal with linking declarations over multiple translation units. For variables it is a way to give the compiler ["a place and a time"](https://www.youtube.com/watch?v=m7hwL0gHuP4) to initialize a static variable. ### smart pointers - smart pointers point to NULL on construction. - They can easily be checked for content with if (mySmartPointer) //has content else //is empty - A unique pointer will be destroyed at the end of it's scope. - A smart pointer will be destroyed when all of it's references are overwritten or the scope of the last reference ends. ### std::optional or "return a value or null" One task when implementing the linked list is to return the Command modified Element. Or, if for whatever reason the operation fails to return null to signal an error to the user of the list. This isnt possible right away in C++ as null cant be casted to any type. To solve this problem in a concise and easy way `std::optional<T>` was introduced in C++17. It contains not only its given type T, but also a boolean to keep track if it is empty. Example: std::optional<Gear> opt = std::optional<Gear>(); // <- empty opt = Gear(1, 2); // <- filled with value if (opt) cout << opt.value.getConfig(); else cout << "is empty"; ### Doxygen Basically Javadoc for C++ and many other languages. Call `doxygen -g` to generate a standard Doxyfile Or `doxygen -g my_conf` for a custom name Then simply run `doxygen` or `doxygen my_conf` to execute. CMake also allows to call doxygen as part of the build routine, see `CMakeLists.txt`. Basic settings in `Doxyfile`: - Project_name, _brief, _logo for project info - Output_directory to specify a subfolder (here docs/) - Optimize_output_for_C seems a good choice - INPUT every directory or file to show up in the documentation (here src/ include/ README.md) - RECURSIVE to also document subdirectories - USE_MDFILE_AS_MAINPAGE because it would be empty otherwise (here README.md) (specify file in INPUT) - SOURCE_BROWSER and INLINE_SOURCES for source code in documentation - GENERATE_TREEVIEW for the sidebar well-known from Javadoc - GENERATE_LATEX to spare generation of latex files ### Dynamic Cast To retrieve subclasses from a base pointer e.g. `ICommand*` we need to cast it to the desired class. For this dynamic_cast<>() can be used. It returns a pointer to the class we want or, if the object is not of that type, it returns nullptr. To check if an `ICommand*` is also a `Gear*` we do: ICommand* icom = new Gear(); Gear* p = dynamic_cast<Gear*>(ICommand*); if (p) //icom was of type Gear*, we can use p. dynamic_pointer_cast can be used with the same syntax. It returnins a shared_ptr to the same Object, this way memory safety is kept. static_cast<>() does also exist, [Kate Gregory](https://youtu.be/XkDEzfpdcSg?t=2375) calls this the "This is my foot and I'm pointing this gun here on purpose"-operator. It should only be used if we are absolutely sure of what we are getting. ### Virtual Inheritance With multiple inheritance it is possible that a class inherits from the same base class twice. In our case this happens in the Commands: Gear -> Command -> ICommand Gear -> IGear -> ICommand In that case it would have two separate instances of the base class. Usually only one instance of the base class is neccessary. To achieve that the keyword `virtual` is used in the class declaration. Like this: class IGear : public virtual ICommand Meaning IGear does not need a separate instance of ICommand. Especially useful for interfaces. More Info: [Virtual Inheritance](https://en.wikipedia.org/wiki/Virtual_inheritance?oldformat=true) ### Qt #### German Youtube Tutorials [CodingProf](https://www.youtube.com/c/CodingProf/) is a professor at Hochschule Ruhr West and has many videos on programming and C++ in general, but also talks about GUIs and Qt: [Playlist with Qt](https://www.youtube.com/playlist?list=PL1urCEuoZfltITfwd7jEA51XSHme8Oq3d). #### Qt Memory and ownership Qt doesn't use unique or shared pointers for memory management, but rather manages the QObjects in an [Object Tree](https://doc.qt.io/archives/qt-6.0/objecttrees.html). This *mostly* takes away concerns about memory safety. As soon as we add a new Widget to an existing e.g. with `addWidget()` one we don't care about the deletion of the object anymore as the ownership is transfered. There *of course* are exceptions: QAction can be represented in a QMenuBar as well as a QToolBar, so none of them take ownership of the QAction. This is stated in the [documentation](https://doc.qt.io/qt-6/qwidget.html#addAction). Here two paths can be chosen: The [recommended](https://doc.qt.io/qt-6/qaction.html) one: "We recommend that actions are created as children of the window they are used in. In most cases actions will be children of the application's main window." Or we create them without a parent specified and manage/delete them on our own, as was done here. If choosing that route care must be taken to not delete twice. #### QString QStrings can be constructed from char[]s, but not from std::string, convert them like: `std::string -> QString .c_Str()` `QString -> std::string .toStdString()` #### Qt Examples Some examples showcasing different basic functionalities are compiled [here](https://github.com/tobiglaser/qt-examples).