uibk_703807-Advanced-CXX/exercises/README.md
2020-12-11 11:23:43 +01:00

12 KiB

Exercises

Task 01

Install G++ and Clang, then compile the provided file hello.cpp. Use the following flags when compiling:

-std=c++17 -Wall -Wextra -O2

Next, set up Boost on your system and compile the provided file hello_boost.cpp. Boost is quite common and provides you a set of useful C++ libraries. Some of its content is even promoted into the C++ standard library.

Task 02

Run Clang on the provided file vec.cpp using the following command:

clang -std=c++17 -Xclang -ast-dump -fsyntax-only -Wno-vexing-parse vec.cpp

Clang will parse the input file and display its abstract syntax tree (AST). In the bottom half of the output you'll find the function declaration of main followed by its CompoundStmt. Take a close look at its children and compare the resulting AST with the input code. Notice any oddities — something that looks counter intuitive?

As you can see, there are multiple different ways of initialisation in C++. Check out the corresponding section at cppreference.

Task 03

The directory task03 hosts four subdirectories, libFoo, libBar, libBaz, and app.

Each folder prefixed with lib represents a library and contains a header plus a source file. Furthermore, the library libBaz.so depends on libBar.so.

app contains a single source file providing a main function. It depends on all three libraries.

Dependency Graph

  • Model this project structure using CMake
  • Be sure to set the C++ standard to C++17 and enable warnings (-Wall -Wextra)
  • The default build type should be Release

CMake itself is a build system generator. You can choose from a variety of target build systems.

Task 04

Examine the program iterations.cpp and think about the expected output. Compile the program and run it. What do you notice? Did you expect this behaviour? Did you get any compiler warnings? Investigate what is actually happening (consider using valgrind or a debugger).

How can such errors be prevented? Look for tools (e.g. static code analysers) which help discovering such faulty code.

Note: If you run the executable and everything seems normal, try changing the initial content of xs, using different optimisation flags, or a different compiler. The actual behaviour of this executable depends on various factors.

See Iterator Invalidation.

Task 05

You are given the program strange.cpp. Compile it with different compilers and optimisation flags. What do you notice? What is really happening here?

See Undefined Behaviour and Defining the undefinedness of C.

Task 06

This task focuses on the correct implementation of RAII as well as copy and move semantics. You are asked to implement the concept of unique_ptr and shared_ptr. Since we won't concern ourselves with templates for the moment your implementation will own an instance of the following struct.

struct Vec2 {
    float x, y;
};
  • Read the documentation regarding smart pointers, unique_ptr, and shared_ptr
  • Implement your version of unique_ptr_to_vec2 and shared_ptr_to_vec2 fulfilling these requirements:
    • Dynamically allocate an instance of Vec2 in your constructor
    • De-allocate the Vec2 instance in your destructor
    • Implement correct copy semantics (copy constructor / copy assignment)
    • Implement correct move semantics (move constructor / move assignment)
    • Enable access to Vec2 via the operators * and ->
    • Thread-safety for shared_ptr_to_vec2's reference counter is not required
    • Pay attention to corner-cases like self-assignment (v = v)
  • Prepare a few interesting test cases
  • Check your implementation for memory leaks and memory corruptions using valgrind and sanitizers

See Rule of Three.

Task 07

Read this blog post.

  • Pay attention to implementation vs. interface inheritance
  • Pay attention to the use of templates (assuming you've already covered them)
    • Think about the benefits and drawbacks of the used patterns

Task 08

You are given the following definition of a person:

struct Person {
    std::string firstname;
    std::string lastname;
    int age;
};
  • Implement relational operators (<, <=, >, >=)
  • Implement comparison operators (==, !=)

Next, create 5 different instances and put all of them

  • in an std::vector;
  • in an std::set; and
  • in an std::map as key (we don't care about the value type of the map).

Use algorithms from the standard library, like std::find and std::partition on these containers and examine which operators are used.

Hint: You may want to have a look at std::tie.

Task 09

Reuse Person from Task 08 and implement the necessary parts for inserting it into an std::unordered_set.

Compare the performance of:

  • std::vector
  • std::list
  • std::set
  • std::unordered_set

Task 10

Have a look at this. Now, do that in C++!

Utilize lambdas, std::function, and/or structs with call operators. Critically think about ownership and minimize the amount of heap allocations.

Task 11

Take a look at Boost's chat server example. Try to understand how the session's lifetime is managed by the server. Focus on std::enable_shared_from_this in combination with lambda captures.

Task 12

Reuse Person from Task 08 and take the following, incomplete definition of a room:

class Room {
  public:
    Room(int id, size_t limit) : id(id), limit(limit) {}

    // Returns true iff the person successfully entered.
    bool enter(/* Person */) {}

    void exit(/* Person */) {}

  private:
    const int id;
    const size_t limit;
    std::vector</* Person */> peopleInside;
};

Room contains a list of people currently located inside. People can enter and exit the room via the respective member functions. However, at most only limit people may be inside at any given time (invariant).

  • Add the missing pieces, paying special attention to the types

The following use cases need to be covered next:

  • Asking a Room how many people are currently located inside
  • Asking a Room whether a specific person is currently located inside
  • Iterating over all people currently located in a Room

Implement whatever is necessary to support these use cases, making sure the invariant remains intact.

Task 13

Reuse Person from Task 08.

Create an std::vector<std::shared_ptr<Person>> with at least 3 different elements. Create a function which takes a const std::vector<std::shared_ptr<Person>>& as input and returns an std::vector<Person*>. Each element in the result vector corresponds to the respective element in the input vector.

For the functional programming nerds, the definition of this function would be something like fmap std::shared_ptr::get.

Write your function in different ways and compare the readability:

  • use a range-based for loop
  • use std::transform
    • use a lambda expression
    • use std::mem_fn

Think about taking the argument by value instead of taking it by const reference.

Task 14

Implement your own version of std::vector without using any of the provided containers — use regular arrays (new[] / delete[]) to house your elements. The focus of this task lies on the use of templates and implementation of iterators. You do not have to concern yourself with custom allocators.

Test your implementation with different types (int, double, and a custom struct).

Take your vector from task 1 and implement iterators. You might want to read through the respective documentation.

Write some tests utilising algorithms provided by the standard library to check if your iterators behave correctly.

Task 15

Take your vector implementation from Task 14 and instantiate it with a big number of unique types.

Inspect the relationship between the number of unique instantiates and compile time. Furthermore, look at the compiled object file using nm.

Task 16

In this task you have to create a rudimentary plugin system.

You are given plugin.hpp which contains an interface for your plugins, as well as the function name of the constructor function and its type. Note that the constructor function returns an std::unique_ptr<Plugin>.

  • create an executable which dynamically loads plugins and executes their run member function
  • create two different plugins (foo and bar) showing off the plugin system

It could look like this:

$ ./main ./libFoo.so
Creation of first plugin
Running the first plugin
Destruction of first plugin

$ ./main ./libFoo.so ./libBar.so
Creation of first plugin
Running the first plugin
Destruction of first plugin
Creation of second plugin
Running the second plugin
Destruction of second plugin

Hint: Have a look at the related man-pages dlopen(3) and dlsym(3).

Task 17

Take your vector from Task 14 and implement component-wise addition via operator+ on your vector. Support implicit type conversions: MyVector<int>{} + MyVector<double>{} yields a MyVector<double>.

Hint: Look into decltype and std::declval.

Task 18

You are given the following code snippet of a mathematical vector.

template <std::size_t N>
class Vector {
  public:
    /* ... */

  private:
    std::array<double, N> data;
};

Find an elegant way to provide the following interface:

  • On default construction (no arguments), all elements are initialized to zero.
  • Besides copy / move semantics, there is only one additional constructor which takes exactly N doubles to initialize data.
  • Accessing elements via the subscript operator operator[].
  • Members .x, .y, .z access data[0], data[1], data[2] respectively:
    • With N == 1 there should be only .x available.
    • With N == 2 there should be .x and .y available.
    • With N == 3 there should be .x, .y, and .z available.

Add a few tests to ensure correct behavior using the following aliases:

using Vec1 = Vector<1>;
using Vec2 = Vector<2>;
using Vec3 = Vector<3>;

*Note: You are allowed to modify the given snippet as necessary.

Task 19

Revisit the meta programming example from the lecture regarding std::tuple.

Given the following class template:

template <typename... Types>
class type_set {};

type_set should behave like a set of types. The empty set would therefore be type_set<>, while the set containing the type int would be type_set<int>, so on and so forth.

  • Create a meta function type_set_contains_v which checks if a given type_set contains a given type.
  • Create a meta function type_set_is_subset_v which checks if a given type_set is a subset of another given type_set.
  • Create a meta function type_set_is_same_v which checks if a given type_set is equal to another given type_set.
  • Create a meta function type_set_size_v which tells the size of a given type_set. For type_set<int, int, float> it should return 2.

Try not to use any of the utilities provided by the standard library (like the example provided in the lecture).

Hint: If you are struggling with this exercise you might want to have a look at how fold (i.e. reduce) is used in functional programming languages.

Task 20

Revisit the Advanced Template slides.

Go through the has_print_to example from the slides step by step. Explain all parts like it's done in the lecture.

Task 21

Take a look at Boost Operators. Try to understand why the curiously recurring template pattern (CRTP) is used.