Skip to content
Snippets Groups Projects
user avatar
tobiglaser authored
7de45efc
History

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.

Changes

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_ptrs rather than shared_ptrs 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 Commands. 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 Commands 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() 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. 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" 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