diff --git a/.github/workflows/build-exercises.yml b/.github/workflows/build-exercises.yml index 5c8d51a5..c67368f6 100644 --- a/.github/workflows/build-exercises.yml +++ b/.github/workflows/build-exercises.yml @@ -2,10 +2,10 @@ name: Build exercises on: push: paths: - - 'code/**' + - 'exercises/**' pull_request: paths: - - 'code/**' + - 'exercises/**' - '.github/workflows/**' # Cancel running jobs on force-push @@ -21,7 +21,7 @@ jobs: matrix: EXERCISE: - NAME: "asan" - - NAME: "atomic" + - NAME: "basicTypes" - NAME: "callgrind" - NAME: "concepts" - NAME: "condition_variable" @@ -39,8 +39,9 @@ jobs: - NAME: "memcheck" - NAME: "modern_oo" - NAME: "move" + - NAME: "classes" + SKIP_DEFAULT: true - NAME: "operators" - SKIP_DEFAULT: true - NAME: "optional" - NAME: "polymorphism" - NAME: "python" @@ -61,8 +62,7 @@ jobs: GENERATOR: -G "Unix Makefiles" env: CXX: g++-11 - # We temporarily need to hardcode macos-12, because "-latest" resolves to 11 - - OS: "macos-12" + - OS: "macos-latest" GENERATOR: -G "Xcode" - OS: "windows-latest" GENERATOR: @@ -97,10 +97,10 @@ jobs: # The build/test steps to execute. steps: # Use a standard checkout of the code. - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Run the CMake configuration. - name: CMake Configure - run: cmake -S ${{ github.workspace }}/code/${{ matrix.EXERCISE.NAME }} + run: cmake -S ${{ github.workspace }}/exercises/${{ matrix.EXERCISE.NAME }} -B build ${{ matrix.PLATFORM.GENERATOR }} # Perform the build of the "main exercise" with CMake. @@ -113,12 +113,12 @@ jobs: if: ${{ !matrix.EXERCISE.SKIP_SOLUTION || false }} # Perform the build of the "main exercise" with GNU Make. - name: GNU Make Build Main - run: make -C ${{ github.workspace }}/code/${{ matrix.EXERCISE.NAME }} + run: make -C ${{ github.workspace }}/exercises/${{ matrix.EXERCISE.NAME }} if: ${{ matrix.PLATFORM.OS != 'windows-latest' && (!matrix.EXERCISE.SKIP_DEFAULT || false) }} # Perform the build of the "solution" with GNU Make. - name: GNU Make Build Solution - run: make -C ${{ github.workspace }}/code/${{ matrix.EXERCISE.NAME }} + run: make -C ${{ github.workspace }}/exercises/${{ matrix.EXERCISE.NAME }} solution if: ${{ matrix.PLATFORM.OS != 'windows-latest' && (!matrix.EXERCISE.SKIP_SOLUTION || false) }} diff --git a/.github/workflows/build-slides.yml b/.github/workflows/build-slides.yml index 3ecb3895..e2814296 100644 --- a/.github/workflows/build-slides.yml +++ b/.github/workflows/build-slides.yml @@ -21,20 +21,29 @@ jobs: version: [ essentials, full ] steps: - name: Set up Git repository - uses: actions/checkout@v3 - - name: Set up essentials course + uses: actions/checkout@v4 + - name: Compile essentials + uses: xu-cheng/latex-action@v3 if: matrix.version == 'essentials' - run: echo '\basictrue' > talk/onlybasics.tex - - name: Compile LaTeX document - uses: xu-cheng/latex-action@v2 with: root_file: C++Course.tex latexmk_shell_escape: true + latexmk_use_xelatex: true + args: -pdf -interaction=nonstopmode -halt-on-error -usepretex=\def\makebasic{} + working_directory: talk + extra_system_packages: "py-pygments" + - name: Compile full course + uses: xu-cheng/latex-action@v3 + if: matrix.version == 'full' + with: + root_file: C++Course.tex + latexmk_shell_escape: true + latexmk_use_xelatex: true args: -pdf -interaction=nonstopmode -halt-on-error working_directory: talk extra_system_packages: "py-pygments" - name: Upload PDF as artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: PDF_${{matrix.version}} path: | diff --git a/.github/workflows/check-links.yaml b/.github/workflows/check-links.yaml new file mode 100644 index 00000000..0990af83 --- /dev/null +++ b/.github/workflows/check-links.yaml @@ -0,0 +1,14 @@ +name: Check Markdown links + +on: + push: + pull_request: + schedule: + - cron: "0 0 1 * *" + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: gaurav-nelson/github-action-markdown-link-check@v1 diff --git a/.github/workflows/publish-slides.yml b/.github/workflows/publish-slides.yml index a1e28189..63d2f45c 100644 --- a/.github/workflows/publish-slides.yml +++ b/.github/workflows/publish-slides.yml @@ -13,12 +13,19 @@ jobs: matrix: version: [ essentials, full ] steps: - - uses: actions/checkout@v3 - - name: Setup essentials course + - uses: actions/checkout@v4 + - name: Compile Essentials document if: matrix.version == 'essentials' - run: echo '\basictrue' > talk/onlybasics.tex - - name: Compile LaTeX document - uses: xu-cheng/latex-action@v2 + uses: xu-cheng/latex-action@v3 + with: + root_file: C++Course.tex + latexmk_use_xelatex: true + args: -f -pdf -interaction=nonstopmode -shell-escape -usepretex=\def\makebasic{} + working_directory: talk + extra_system_packages: "py-pygments" + - name: Compile Full document + if: matrix.version == 'full' + uses: xu-cheng/latex-action@v3 with: root_file: C++Course.tex latexmk_use_xelatex: true diff --git a/.gitignore b/.gitignore index ec42d46e..c32d5754 100644 --- a/.gitignore +++ b/.gitignore @@ -1,56 +1,8 @@ -# latex related, Course -C++Course.* -!C++Course.tex -_minted* - -# latex related, exercise intro -exercisesIntro.log -exercisesIntro.nav -exercisesIntro.out -exercisesIntro.snm -exercisesIntro.toc -exercisesIntro.vrb -exercisesIntro.pdf - -# exercises -build -*.a -*.o -*.so -*.sol -*.sol? -code/*aux -code/callgrind/fibocrunch -code/control/control -code/loopsRefsAuto/loopsRefsAuto -code/operators/operators -code/concepts/concepts -code/constness/constplay -code/cppcheck/randomize -code/debug/randomize -code/functions/functions -code/helgrind/fiboMT -code/hello/hello -code/lambdas/randomize -code/memcheck/memleak -code/move/trymove -code/optional/optional -code/polymorphism/trypoly -code/race/racing -code/stl/randomize.nostl -code/templates/playwithsort -code/valgrind/randomize -code/variant/variant -code/virtual_inheritance/trymultiherit -code/smartPointers/smartPointers - -# tools -CMakeCache.txt -CMakeFiles -cmake_install.cmake -callgrind.out.* - # just for the allcontributors cli installation... package-lock.json package.json node_modules/** + +# Temporary latex files: +talk/C++Course.* +talk/_minted diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60e9ba43..20be3b7a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v5.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -9,7 +9,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/codespell-project/codespell - rev: 'v2.2.4' + rev: 'v2.4.1' hooks: - id: codespell args: ["-I", "codespell.txt"] diff --git a/CREDITS.md b/CREDITS.md index 00130408..df274922 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,7 +1,7 @@ Credits ======= - - Adaptations for CERN sessions (2020, 2021) + - Adaptations for CERN sessions (2020, 2025) * [Sebastien Ponce](https://github.com/sponce) [CERN](http://cern.ch)/[LHCb](http://lhcb.cern.ch) * [David Chamont](https://gitlab.cern.ch/chamont) [IN2P3](https://informatique.in2p3.fr) * [Attila Krasznahorkay](https://gitlab.cern.ch/akraszna) [CERN](http://cern.ch)/[Atlas](https://atlas.cern/) diff --git a/README.md b/README.md index d68978dd..e08580bc 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ This repository contains all material for the C++ Course taught at CERN from Sebastien Ponce (LHCb). +## Next events + +Please check the [Indico agenda](https://indico.cern.ch/category/11733/). + ## 📎 Getting the latest PDF For each commit to master, the slides are compiled to a PDF and uploaded to the [download](https://github.com/hsf-training/cpluspluscourse/tree/download) branch. @@ -24,10 +28,13 @@ For each commit to master, the slides are compiled to a PDF and uploaded to the Video recordings are available in the past events. -* [5th HEP C++ Course and Hands-on Training (2022 October - advanced)](https://indico.cern.ch/event/1172498/) -* [4nd HEP C++ Course and Hands-on Training (2022 March - essentials)](https://indico.cern.ch/event/1119339/) -* [3rd HEP C++ Course and Hands-on Training (2021 August)](https://indico.cern.ch/event/1019089/) -* [2nd HEP C++ Course and Hands-on Training (2021 January)](https://indico.cern.ch/event/979067/) +* [8th HEP C++ Course and Hands-on Training (essentials), Manchester, August 2023](https://indico.cern.ch/event/1266661/) +* [7th HEP C++ Course and Hands-on Training (essentials), JLAB, May 2023](https://indico.cern.ch/event/1266632/) +* [6th HEP C++ Course and Hands-on Training (essentials), CERN, March 2023](https://indico.cern.ch/event/1229412/) +* [5th HEP C++ Course and Hands-on Training (advanced), CERN, October 2022](https://indico.cern.ch/event/1172498/) +* [4nd HEP C++ Course and Hands-on Training (essentials), CERN, March 2022](https://indico.cern.ch/event/1119339/) +* [3rd HEP C++ Course and Hands-on Training, CERN, August 2021](https://indico.cern.ch/event/1019089/) +* [2nd HEP C++ Course and Hands-on Training, virtual, January 2021](https://indico.cern.ch/event/979067/) * [1st HEP C++ Course and Hands-on Training (2020 October)](https://indico.cern.ch/event/946584/) Check [this page](https://hepsoftwarefoundation.org/Schools/events.html) for announcements of upcoming training events (including those with this material). @@ -86,8 +93,8 @@ make solution ### For Mentors -Depending on which course is running, consult the schedule for exercise sessions in [essentials](code/ExerciseSchedule_EssentialCourse.md) or [advanced](code/ExerciseSchedule_AdvancedCourse.md). -For mentors, there is a [cheat sheet](code/ExercisesCheatSheet.md) with hints towards the solutions and key points to discuss during the exercise sessions. +Depending on which course is running, consult the schedule for exercise sessions in [essentials](exercises/ExerciseSchedule_EssentialCourse.md) or [advanced](exercises/ExerciseSchedule_AdvancedCourse.md). +For mentors, there is a [cheat sheet](exercises/ExercisesCheatSheet.md) with hints towards the solutions and key points to discuss during the exercise sessions. ## Contributors ✨ diff --git a/code/Makefile b/code/Makefile deleted file mode 100644 index 229fc7b2..00000000 --- a/code/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -TESTDIRS = callgrind cppcheck header_units control hello modules move python smartPointers templates virtual_inheritance \ - debug helgrind memcheck polymorphism race stl valgrind -NOCOMPILETESTDIRS = constness - -solution: - for dir in ${TESTDIRS}; do \ - cd $${dir}; \ - make $@; \ - cd ..; \ - done - -clean: - for dir in ${TESTDIRS} ${NOCOMPILETESTDIRS}; do \ - cd $${dir}; \ - make clean; \ - cd ..; \ - done - -clobber: clean diff --git a/code/atomic/CMakeLists.txt b/code/atomic/CMakeLists.txt deleted file mode 100644 index 91946368..00000000 --- a/code/atomic/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Set up the project. -cmake_minimum_required( VERSION 3.12 ) -project( atomic LANGUAGES CXX ) - -# Set up the compilation environment. -include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) - -# Figure out how to use the platform's thread capabilities. -find_package( Threads REQUIRED ) - -# Create the user's executable. -add_executable( atomic "atomic.cpp" ) -target_link_libraries( atomic PRIVATE Threads::Threads ) - -# Create the "solution executable". -add_executable( atomic.sol EXCLUDE_FROM_ALL "solution/atomic.sol.cpp" ) -target_link_libraries( atomic.sol PRIVATE Threads::Threads ) -add_dependencies( solution atomic.sol ) diff --git a/code/atomic/Makefile b/code/atomic/Makefile deleted file mode 100644 index 7462bc2b..00000000 --- a/code/atomic/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -PROGRAM_NAME=atomic - -all: $(PROGRAM_NAME) -solution: $(PROGRAM_NAME).sol - - -clean: - rm -f *o $(PROGRAM_NAME) *~ core $(PROGRAM_NAME).sol - -$(PROGRAM_NAME) : $(PROGRAM_NAME).cpp - ${CXX} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $< - -$(PROGRAM_NAME).sol : solution/$(PROGRAM_NAME).sol.cpp - ${CXX} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $< diff --git a/code/atomic/README.md b/code/atomic/README.md deleted file mode 100644 index 76b13aa2..00000000 --- a/code/atomic/README.md +++ /dev/null @@ -1,13 +0,0 @@ - -## Instructions - -You know this program already from "racing". -It tries to increment an integer 200 times in two threads. -Last time, we fixed the race condition using a lock, but now we'll try atomics. - -Tasks: -- Replace the counter 'a' by an atomic. - Run the program, and check for race conditions. -- Go back to 'racing', and check the execution time of the atomic vs the lock solution, - e.g. using `time ./atomic` - You might have to increase the number of tries if it completes too fast. diff --git a/code/atomic/atomic.cpp b/code/atomic/atomic.cpp deleted file mode 100644 index 713ef65f..00000000 --- a/code/atomic/atomic.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include - -int main() { - int nError = 0; - - for (int j = 0; j < 1000; j++) { - int a = 0; - - // Increment the variable a 100 times: - auto inc100 = [&a](){ - for (int i = 0; i < 100; ++i) { - a++; - } - }; - - // Run with two threads - std::thread t1(inc100); - std::thread t2(inc100); - for (auto t : {&t1,&t2}) t->join(); - - // Check - if (a != 200) { - std::cout << "Race: " << a << ' '; - nError++; - } else { - std::cout << '.'; - } - } - std::cout << '\n'; - - return nError; -} diff --git a/code/atomic/solution/atomic.sol.cpp b/code/atomic/solution/atomic.sol.cpp deleted file mode 100644 index 8fc901ef..00000000 --- a/code/atomic/solution/atomic.sol.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include - -int main() { - int nError = 0; - - for (int j = 0; j < 1000; j++) { - std::atomic a{0}; - - // Increment the variable a 100 times: - auto inc100 = [&a](){ - for (int i = 0; i < 100; ++i) { - a++; - } - }; - - // Run with two threads - std::thread t1(inc100); - std::thread t2(inc100); - for (auto t : {&t1,&t2}) t->join(); - - // Check - if (a != 200) { - std::cout << "Race: " << a << ' '; - nError++; - } else { - std::cout << '.'; - } - } - std::cout << '\n'; - - return nError; -} diff --git a/code/constness/README.md b/code/constness/README.md deleted file mode 100644 index 991daa9e..00000000 --- a/code/constness/README.md +++ /dev/null @@ -1,6 +0,0 @@ - -## Instructions - -* open `constplay.cpp` -* try to find out which lines will be problematic -* try to compile and check your findings diff --git a/code/constness/constplay.cpp b/code/constness/constplay.cpp deleted file mode 100644 index 2ce465b4..00000000 --- a/code/constness/constplay.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include - -int identity(int a) { - return a; -}; - -int identityConst(const int a) { - return a; -}; - -int* identityp(int* a) { - return a; -}; - -const int* identitypConst(const int *a) { - return a; -}; - -struct ConstTest { - void hello(std::string &s) { - std::cout << "Hello " << s << '\n'; - } - void helloConst(std::string &s) const { - std::cout << "Hello " << s << '\n'; - } -}; - -int main() { - // try pointer to constant - int a = 1, b = 2; - int const *i = &a; - *i = 5; - i = &b; - - // try constant pointer - int * const j = &a; - *j = 5; - j = &b; - - // try constant pointer to constant - int const * const k = &a; - *k = 5; - k = &b; - - // try constant arguments of functions - int l = 0; - const int m = 0; - identity(l); - identity(m); - identityConst(l); - identityConst(m); - - // try constant arguments of functions with pointers - int *p = 0; - const int *r = 0; - identityp(p); - identityp(r); - identitypConst(p); - identitypConst(r); - - // try constant method in a class - ConstTest t; - const ConstTest tc; - std::string s("World"); - t.hello(s); - tc.hello(s); - t.helloConst(s); - tc.helloConst(s); -} diff --git a/code/debug/Makefile b/code/debug/Makefile deleted file mode 100644 index 0fd5e8ba..00000000 --- a/code/debug/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -all: randomize -solution: randomize.sol - -clean: - rm -f *o randomize *~ randomize.sol core - -randomize : randomize.cpp - ${CXX} -std=c++17 -g -O0 -L. -o $@ $< - -randomize.sol : solution/randomize.sol.cpp - ${CXX} -std=c++17 -g -O0 -Wall -Wextra -L. -o $@ $< diff --git a/code/debug/README.md b/code/debug/README.md deleted file mode 100644 index 14377e79..00000000 --- a/code/debug/README.md +++ /dev/null @@ -1,14 +0,0 @@ -## Instructions for the "debug" exercise - -* compile, run, see the crash -* run it in gdb -* inspect backtrace, variables -* find problem and fix bug -* try stepping, breakpoints - - -### Go back to the "valgrind" exercise - -* check it with valgrind -* analyze the issue, see that the variance was biaised -* fix the issue diff --git a/code/debug/randomize.cpp b/code/debug/randomize.cpp deleted file mode 100644 index 8832a81b..00000000 --- a/code/debug/randomize.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include -#include - -constexpr auto LEN = 1000; -constexpr auto STEP = 7; - -void swap(int *a, int*b) { - int c = *a; - *a = *b; - *b = c; -} - -void randomize(int* v, unsigned int len) { - std::default_random_engine e; - std::uniform_int_distribution d{0u, len}; - // we randomize via len random inversions - for (unsigned int i = 0; i < len; i++) { - int a = d(e); - int b = d(e); - swap(v+a, v+b); - } -} - -void createAndFillVector(int** v, unsigned int len) { - *v = new int[LEN]; - for (unsigned int i = 0; i < len; i++) (*v)[i] = i*STEP; -} - -int main() { - int *v = nullptr; - // create and randomize vector - randomize(v, LEN+1); - createAndFillVector(&v, LEN+1); - - // compute diffs - int *diffs = new int[LEN]; - for (unsigned int i = 0; i < LEN; i++) - diffs[i] = v[i+1] - v[i]; - - // compute standard deviation of it - float sum = 0; - float sumsq = 0; - for (unsigned int i = 0; i < LEN; i ++) { - sum += diffs[i]; - sumsq += diffs[i]*diffs[i]; - } - float mean = sum/LEN; - float stddev = std::sqrt(sumsq/LEN - mean*mean) ; - std::cout << "Range = [0, " << STEP*LEN << "]\n" - << "Mean = " << mean - << "\nStdDev = " << stddev << '\n'; - - delete[] v; - delete[] diffs; -} diff --git a/code/debug/solution/randomize.sol.cpp b/code/debug/solution/randomize.sol.cpp deleted file mode 100644 index 77d89f1d..00000000 --- a/code/debug/solution/randomize.sol.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include -#include - -constexpr auto LEN = 1000; -constexpr auto STEP = 7; - -void swap(int *a, int*b) { - int c = *a; - *a = *b; - *b = c; -} - -void randomize(int* v, unsigned int len) { - std::default_random_engine e; - std::uniform_int_distribution d{0u, len}; - // we randomize via len random inversions - for (unsigned int i = 0; i < len; i++) { - int a = d(e); - int b = d(e); - swap(v+a, v+b); - } -} - -void createAndFillVector(int** v, unsigned int len) { - *v = new int[len]; - for (unsigned int i = 0; i < len; i++) (*v)[i] = i*STEP; -} - -int main() { - int *v = nullptr; - // create and randomize vector - createAndFillVector(&v, LEN+1); - randomize(v, LEN+1); - - // compute diffs - int *diffs = new int[LEN]; - for (unsigned int i = 0; i < LEN; i++) - diffs[i] = v[i+1] - v[i]; - - // compute standard deviation of it - float sum = 0; - float sumsq = 0; - for (unsigned int i = 0; i < LEN; i ++) { - sum += diffs[i]; - sumsq += diffs[i]*diffs[i]; - } - float mean = sum/LEN; - float stddev = std::sqrt(sumsq/LEN - mean*mean) ; - std::cout << "Range = [0, " << STEP*LEN << "]\n" - << "Mean = " << mean - << "\nStdDev = " << stddev << '\n'; - - delete[] v; - delete[] diffs; -} diff --git a/code/functions/solution/functions.sol.cpp b/code/functions/solution/functions.sol.cpp deleted file mode 100644 index 639cd1e3..00000000 --- a/code/functions/solution/functions.sol.cpp +++ /dev/null @@ -1,47 +0,0 @@ - -/* Tasks: - * 1. Check out Structs.h. It defines two structs that we will work with. - * FastToCopy - * SlowToCopy - * They are exactly what their name says, so let's try to avoid copying the latter. - * 2. Using "printFiveCharacters()" as an example, write a function that prints the first five characters of "SlowToCopy". - * Call it in main(). - * 3. Try passing by copy and passing by reference, see the difference. - * 4. When passing by reference, ensure that your "printFiveCharacters" cannot inadvertently modify the original object. - * To test its const correctness, try adding something like - * argument.name[0] = 'a'; - * to your print function. - * Try both with and without const attributes in your print function's signature. - */ - -#include "Structs.h" // The data structs we will work with - -#include // For printing - -void printFiveCharacters(FastToCopy argument) { - std::cout << argument.name << "\n"; -} - -void printFiveCharacters(const SlowToCopy & argument) { - //argument.data[0] = '\n' ; // EXPECTED COMPILATION ERROR - std::cout << argument.name << "\n"; -} - -void printFiveCharactersWithCopy(SlowToCopy argument) { - std::cout << argument.name << "\n"; - // We can actually modify the argument if we want, since it's a copy: - argument.name[0] = 'a'; -} - -int main() { - FastToCopy fast = {"abcdef"}; - printFiveCharacters(fast); - - SlowToCopy slow = {"ghijkl"}; - printFiveCharacters(slow); - - std::cout << "Now printing with copy:\n"; - printFiveCharactersWithCopy(slow); - - return 0; -} diff --git a/code/operators/Makefile b/code/operators/Makefile deleted file mode 100644 index 6766b5fb..00000000 --- a/code/operators/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -all: operators -solution: operators.sol - -clean: - rm -f *o operators *~ operators.sol - -operators : operators.cpp - ${CXX} -g -std=c++17 -O0 -Wall -Wextra -L. -o $@ $< - -operators.sol : solution/operators.sol.cpp - ${CXX} -g -std=c++17 -O0 -Wall -Wextra -L. -o $@ $< diff --git a/code/operators/README.md b/code/operators/README.md deleted file mode 100644 index 1c9cd8ae..00000000 --- a/code/operators/README.md +++ /dev/null @@ -1,6 +0,0 @@ - -## Instructions - -* inspect main and complete the implementation of class Fraction step by step - * you can comment out parts of main to test in between -* when possible, prefer non-member operators and friend for operators diff --git a/code/operators/operators.cpp b/code/operators/operators.cpp deleted file mode 100644 index af4deb09..00000000 --- a/code/operators/operators.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include -#include - -class Fraction { - public: - // TODO: constructors and operators - - private: - void normalize() { - const int gcd = std::gcd(m_num, m_denom); - m_num /= gcd; - m_denom /= gcd; - } - - int m_num, m_denom; -}; - -// TODO: operators - - -void printAndCheck(std::string const & what, Fraction const & result, Fraction const & expected) { - const bool passed = result == expected; - std::cout << std::left << std::setw(40) << what << ": " << (passed ? "PASS" : "** FAIL **") << " " << result << "\n"; -} -void printAndCheck(std::string const & what, bool result, bool expected) { - const bool passed = result == expected; - std::cout << std::left << std::setw(40) << what << ": " << (passed ? "PASS" : "** FAIL **") << " " << result << "\n"; -} - -int main() { - // create a fraction with values 3 (which is 3/1) and 1/3 - const Fraction three{3}; - const Fraction athird{1, 3}; - - // print the fractions - std::cout << "Three: " << three << '\n'; - std::cout << "One third: " << athird << '\n'; - - // multiply fraction with an int - // the printAndCheck function requires operator<< and operator==: - printAndCheck("One third times two", athird * 2, Fraction{2, 3}); - // ensure symmetry - printAndCheck("Two times one third", 2 * athird, Fraction{2, 3}); - - // multiply two fractions - printAndCheck("Three times one third", three * athird, Fraction{1, 1}); - // normalize the fraction after multiplication so the above statement - // prints 1/1 instead of e.g. 3/3 - printAndCheck("Three times one third", 3 * athird, Fraction{1, 1}); - - // multiply in place - Fraction f = athird; - f *= 2; - printAndCheck("One third times two", f, Fraction{2, 3}); - - f *= athird; - printAndCheck("One third times one third", f, Fraction{2, 9}); - - // you might have some redundancy between the implementation of operator* and - // operator*=. Can you refactor your code and implement operator* in terms of - // operator*=? - - std::cout << std::boolalpha; // print bools as 'true' or 'false' from now on - - // more equality comparisons - printAndCheck("One third == one third", (athird == Fraction{1, 3}), true); - printAndCheck("One third != one forth", (athird != Fraction{1, 4}), true); - printAndCheck("One third == two sixth", (athird == Fraction{2, 6}), true); - printAndCheck("One third != three sixth", (athird != Fraction{3, 6}), true); - // try to implement operator!= in terms of operator== - - // more comparisons - const Fraction afourth{1, 4}; - printAndCheck("athird < athird", (athird < athird), false); - printAndCheck("afourth < athird", (afourth < athird), true); - printAndCheck("athird <= athird", (athird <= athird), true); - printAndCheck("athird <= afourth", (athird <= afourth), false); - printAndCheck("athird > athird", (athird > athird), false); - printAndCheck("afourth > athird", (afourth > athird), false); - printAndCheck("athird >= athird", (athird >= athird), true); - printAndCheck("athird >= afourth", (athird >= afourth), true); - // the operators <=, >= and > can typically be implemented just in terms of - // operator<. Can you do this as well? ;) - - // take aways on operators: - // * we can very often implement an arithemtic operator@ in terms of - // operator@= - // * it usually suffices to implement operator< and operator== and derive the - // other relational operators from them. C++20 will do part of this automatically. -} diff --git a/code/operators/solution/operators.sol.cpp b/code/operators/solution/operators.sol.cpp deleted file mode 100644 index eced479d..00000000 --- a/code/operators/solution/operators.sol.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include -#include -#include - -class Fraction { - public: - explicit Fraction(int i) : m_num(i), m_denom(1) {} - Fraction(int num, int denom) : m_num(num), m_denom(denom) { - normalize(); - } - - friend std::ostream& operator<<(std::ostream& os, Fraction const & f) { - os << f.m_num << "/" << f.m_denom; - return os; - } - - Fraction & operator*=(int i) { - m_num *= i; - normalize(); - return *this; - } - - Fraction & operator*=(Fraction const & o) { - m_num *= o.m_num; - m_denom *= o.m_denom; - normalize(); - return *this; - } - - friend Fraction operator*(Fraction f, int i) { return f *= i; } - friend Fraction operator*(int i, Fraction const & f) { return f * i; } - friend Fraction operator*(Fraction a, Fraction const & b) { return a *= b; } - - friend bool operator==(Fraction const & a, Fraction const & b) { - return a.m_num == b.m_num && a.m_denom == b.m_denom; - } - - friend bool operator<(Fraction const & a, Fraction const & b) { - return a.m_num * b.m_denom < b.m_num * a.m_denom; - } - - friend bool operator!=(Fraction const & a, Fraction const & b) { return !(a == b); } - friend bool operator>(Fraction const & a, Fraction const & b) { return b < a; } - friend bool operator<=(Fraction const & a, Fraction const & b) { return !(a > b); } - friend bool operator>=(Fraction const & a, Fraction const & b) { return !(a < b); } - - private: - void normalize() { - const int gcd = std::gcd(m_num, m_denom); - m_num /= gcd; - m_denom /= gcd; - } - - int m_num, m_denom; -}; - -void printAndCheck(std::string const & what, Fraction const & result, Fraction const & expected) { - const bool passed = result == expected; - std::cout << std::left << std::setw(40) << what << ": " << (passed ? "PASS" : "** FAIL **") << " " << result << "\n"; -} -void printAndCheck(std::string const & what, bool result, bool expected) { - const bool passed = result == expected; - std::cout << std::left << std::setw(40) << what << ": " << (passed ? "PASS" : "** FAIL **") << " " << result << "\n"; -} - -int main() { - // create a fraction with values 3 (which is 3/1) and 1/3 - const Fraction three{3}; - const Fraction athird{1, 3}; - - // print the fractions - std::cout << "Three: " << three << '\n'; - std::cout << "One third: " << athird << '\n'; - - // multiply fraction with an int - // the printAndCheck function requires operator<< and operator==: - printAndCheck("One third times two", athird * 2, Fraction{2, 3}); - // ensure symmetry - printAndCheck("Two times one third", 2 * athird, Fraction{2, 3}); - - // multiply two fractions - printAndCheck("Three times one third", three * athird, Fraction{1, 1}); - // normalize the fraction after multiplication so the above statement - // prints 1/1 instead of e.g. 3/3 - printAndCheck("Three times one third", 3 * athird, Fraction{1, 1}); - - // multiply in place - Fraction f = athird; - f *= 2; - printAndCheck("One third times two", f, Fraction{2, 3}); - - f *= athird; - printAndCheck("One third times one third", f, Fraction{2, 9}); - - // you might have some redundancy between the implementation of operator* and - // operator*=. Can you refactor your code and implement operator* in terms of - // operator*=? - - std::cout << std::boolalpha; // print bools as 'true' or 'false' from now on - - // more equality comparisons - printAndCheck("One third == one third", (athird == Fraction{1, 3}), true); - printAndCheck("One third != one forth", (athird != Fraction{1, 4}), true); - printAndCheck("One third == two sixth", (athird == Fraction{2, 6}), true); - printAndCheck("One third != three sixth", (athird != Fraction{3, 6}), true); - // try to implement operator!= in terms of operator== - - // more comparisons - const Fraction afourth{1, 4}; - printAndCheck("athird < athird", (athird < athird), false); - printAndCheck("afourth < athird", (afourth < athird), true); - printAndCheck("athird <= athird", (athird <= athird), true); - printAndCheck("athird <= afourth", (athird <= afourth), false); - printAndCheck("athird > athird", (athird > athird), false); - printAndCheck("afourth > athird", (afourth > athird), false); - printAndCheck("athird >= athird", (athird >= athird), true); - printAndCheck("athird >= afourth", (athird >= afourth), true); - // the operators <=, >= and > can typically be implemented just in terms of - // operator<. Can you do this as well? ;) - - // take aways on operators: - // * we can very often implement an arithemtic operator@ in terms of - // operator@= - // * it usually suffices to implement operator< and operator== and derive the - // other relational operators from them. C++20 will do part of this automatically. -} diff --git a/code/polymorphism/Polygons.cpp b/code/polymorphism/Polygons.cpp deleted file mode 100644 index e285ca18..00000000 --- a/code/polymorphism/Polygons.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "Polygons.hpp" -#include -#include - -Polygon::Polygon(int n, float radius) : m_nbSides(n), m_radius(radius) {}; - -float Polygon::computePerimeter() const { - std::cout << "Polygon::computePerimeter is being called\n"; - return 2*m_nbSides*std::sin(static_cast(M_PI)/m_nbSides)*m_radius; -} - -Pentagon::Pentagon(float radius) : Polygon(5, radius) {} - -Hexagon::Hexagon(float radius) : Polygon(6, radius) {} - -float Hexagon::computePerimeter() const { - std::cout << "Hexagon::computePerimeter is being called\n"; - return 6 * m_radius; -} diff --git a/code/race/CMakeLists.txt b/code/race/CMakeLists.txt deleted file mode 100644 index c7415d6f..00000000 --- a/code/race/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Set up the project. -cmake_minimum_required( VERSION 3.12 ) -project( race LANGUAGES CXX ) - -# Set up the compilation environment. -include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) - -# Figure out how to use the platform's thread capabilities. -find_package( Threads REQUIRED ) - -# Create the user's executable. -add_executable( racing "racing.cpp" ) -target_link_libraries( racing PRIVATE Threads::Threads ) - -# Create the "solution executable". -add_executable( racing.sol EXCLUDE_FROM_ALL "solution/racing.sol.cpp" ) -target_link_libraries( racing.sol PRIVATE Threads::Threads ) -add_dependencies( solution racing.sol ) diff --git a/code/race/Makefile b/code/race/Makefile deleted file mode 100644 index 4bf10084..00000000 --- a/code/race/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -PROGRAM_NAME=racing - -all: $(PROGRAM_NAME) -solution: $(PROGRAM_NAME).sol - - -clean: - rm -f *o $(PROGRAM_NAME) *~ core $(PROGRAM_NAME).sol - -$(PROGRAM_NAME) : $(PROGRAM_NAME).cpp - ${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $< - -$(PROGRAM_NAME).sol : solution/$(PROGRAM_NAME).sol.cpp - ${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $< diff --git a/code/race/README.md b/code/race/README.md deleted file mode 100644 index 95f6d2aa..00000000 --- a/code/race/README.md +++ /dev/null @@ -1,12 +0,0 @@ - -## Instructions - -* Compile and run the executable, see if it races - * If you have a bash shell, try `./run ./racing`, which keeps invoking the executable - until a race condition is detected -* (Optional) You can use `valgrind --tool=helgrind ./racing` to prove your assumption -* (Optional) If your operating system supports it, recompile with thread sanitizer. - With Makefile, use e.g. `make CXXFLAGS="-fsanitize=thread"` -* Use a mutex to fix the issue -* See the difference in execution time -* (Optional) Check again with `valgrind` or thread sanitizer if the problem is fixed diff --git a/code/race/solution/racing.sol.cpp b/code/race/solution/racing.sol.cpp deleted file mode 100644 index 8eb6fdf7..00000000 --- a/code/race/solution/racing.sol.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include - -/* - * This program tries to increment an integer 100 times from multiple threads. - * If the result comes out at 100*nThread, it stays silent, but it will print - * an error if a race condition is detected. - * To run it in a loop, use - * ./run ./racing.sol - */ - -constexpr unsigned int nThread = 2; - -int main() { - int nError = 0; - - for (int j = 0; j < 1000; j++) { - int a = 0; - std::mutex aMutex; - - // Increment the variable a 100 times: - auto inc100 = [&a,&aMutex](){ - for (int i = 0; i < 100; ++i) { - std::scoped_lock lock{aMutex}; - a++; - } - }; - - // Start up all threads: - std::vector threads; - for (unsigned int i = 0; i < nThread; ++i) threads.emplace_back(inc100); - for (auto & thread : threads) thread.join(); - - // Check - if (a != nThread * 100) { - std::cerr << "Race detected! Result: " << a << '\n'; - nError++; - } - } - - return nError; -} diff --git a/code/smartPointers/CMakeLists.txt b/code/smartPointers/CMakeLists.txt deleted file mode 100644 index ed01281b..00000000 --- a/code/smartPointers/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -# Set up the project. -cmake_minimum_required( VERSION 3.12 ) -project( smartPointers LANGUAGES CXX ) - -# Set up the compilation environment. -include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) -set( CMAKE_CXX_STANDARD 20 ) - -# Create the user's executable. -add_executable( smartPointers "smartPointers.cpp" ) - -# Create the "solution executable". -add_executable( smartPointers.sol EXCLUDE_FROM_ALL "solution/smartPointers.sol.cpp" ) -add_dependencies( solution smartPointers.sol ) diff --git a/code/smartPointers/Makefile b/code/smartPointers/Makefile deleted file mode 100644 index 9e8f8ad0..00000000 --- a/code/smartPointers/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -all: smartPointers -solution: smartPointers.sol - -clean: - rm -f *o *so smartPointers *~ smartPointers.sol - -% : %.cpp - $(CXX) -g -std=c++20 -Wall -Wextra -o $@ $< - -%.sol : solution/%.sol.cpp - $(CXX) -g -std=c++20 -Wall -Wextra -o $@ $< diff --git a/code/smartPointers/README.md b/code/smartPointers/README.md deleted file mode 100644 index 0cc9cd34..00000000 --- a/code/smartPointers/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Writing leak-free C++. - -Here we have four code snippets that will benefit from using smart pointers. -By replacing every explicit `new` with `make_unique` or `make_shared`, -(alternatively by explicitly instantiating smart pointers) we will fix memory leaks, -and make most cleanup code unnecessary. - -## Prerequisites - -* Which pointer is used for what? - * Raw pointer - * [`std::unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr) - * [`std::shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr) -* C++-14 for `std::make_unique` / `std::make_shared`. Understand what these functions do. -* Helpful: Move semantics for `problem2()`, but can do without. - -## Instructions - -* Compile and run the program. It doesn't generate any output. -* Run with valgrind to check for leaks -``` -valgrind --leak-check=full --track-origins=yes ./smartPointers -``` -* In the **essentials course**, work on `problem1()` and `problem2()`, and fix the leaks using smart pointers. -* In the **advanced course**, work on `problem1()` to `problem4()`. Skip `problem4()` if you don't have enough time. diff --git a/code/smartPointers/smartPointers.cpp b/code/smartPointers/smartPointers.cpp deleted file mode 100644 index 5c675c88..00000000 --- a/code/smartPointers/smartPointers.cpp +++ /dev/null @@ -1,337 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/* - * Please fix all memory leaks / ownership problems using smart pointers. - * (Verify by running the program with valgrind!) - * - * In the *essentials course*: - * - Work on problem1() and problem2() - * - You can have a look at problem3() if interested - * - * In the *advanced course*: - * - Work on problem1() to problem4() - * - * In main() at the bottom, comment in the different parts as you progress through the exercise. - * - * Remember that: - * - The unique ownership of data is expressed using unique_ptr. - * - "Observer" access without ownership is expressed using raw pointers, references or spans. - * - Shared ownership to data is expressed using shared_ptr. - */ - - -/* -------------------------------------------------------------------------------------------- - * 1: Always use smart pointers instead of new. - * - * A frequent source of leaks is a function that terminates in an unexpected way. - * - * - Fix the leak using a smart pointer. - * - The arguments of sumEntries() don't need to change, as it has only read access. - * -------------------------------------------------------------------------------------------- - */ - -// Note how we are using a span pointing to "double const" to ensure that the -// data can only be read. You don't need to change anything in this function. -double sumEntries(std::span range) { - // Simulate an error - throw std::invalid_argument("Error when summing over data."); - - return std::reduce(range.begin(), range.end()); -} - -// Often, data are owned by one entity, and merely used by others. In this case, we hand the data to -// sumEntries() for reading, so the ownership stays with this function. Unfortunately, something goes -// wrong and we didn't use smart pointers. -// Understand and fix the memory leak. -void doStuffWithData() { - auto data = new std::array{}; - - sumEntries(*data); - - delete data; -} - - -void problem1() { - try { - doStuffWithData(); - } catch (const std::exception& e) { - std::cerr << "problem1() terminated with exception: \"" << e.what() - << "\" Check for memory leaks.\n"; - } -} - - - -/* -------------------------------------------------------------------------------------------- - * 2: Storing unique_ptr in collections. - * - * Often, one has to store pointers to objects in collections. Fix the memory leaks using unique_ptr. - * - * Notes: - * - Factory functions should return objects either directly or using smart pointers. - * This is good practice, because it clearly shows who owns an object. Fix the return type of the factory function. - * - The vector should own the objects, so try to store them using smart pointers. - * - Since the change function doesn't accept smart pointers, find a solution to pass the objects. - * Note that this works without shared_ptr! - * -------------------------------------------------------------------------------------------- - */ - -// This is a large object. We maybe shouldn't copy it, so using a pointer is advisable to pass it around. -struct LargeObject { - std::array fData; -}; - -// A factory function to create large objects. -LargeObject* createLargeObject() { - auto object = new LargeObject(); - // Imagine there is more setup steps of "object" here - // ... - - return object; -} - -// A function to do something with the objects. -// Note that since we don't own the object, we don't need a smart pointer as argument. -void changeLargeObject(LargeObject& object) { - object.fData[0] = 1.; -} - -void problem2() { - std::vector largeObjects; - - for (unsigned int i=0; i < 10; ++i) { - auto newObj = createLargeObject(); - largeObjects.push_back(newObj); - } - - for (const auto& obj : largeObjects) { - changeLargeObject(*obj); - } -} - - - -/* -------------------------------------------------------------------------------------------- - * 3: Shared ownership. - * - * Most of the time, ownership can be solved by having one owner (with unique_ptr) and one or - * more observers (raw pointers or references). Sometimes, we need to truly share data, though. - * - * Here is an example of a completely messed up ownership model. It leaks about 1/10 of the times - * it is invoked. - * - Verify this by running it in a loop using a command like: - * while true; do valgrind --leak-check=full --track-origins=yes ./smartPointers 2>&1 | grep -B 5 -A 5 problem3 && exit 1; done - * - Fix the ownership model using shared_ptr! - * - Convert the vectors to holding shared_ptr. - * - Fix the arguments of the functions. - * - Speed optimisation: - * Make sure that you don't create & destroy a shared_ptr in the for loop in problem3() and when calling processElement(). - * -------------------------------------------------------------------------------------------- - */ - -// This removes the element in the middle of the vector. -void removeMiddle(std::vector& collection) { - auto middlePosition = collection.begin() + collection.size()/2; - - // Must not delete element when erasing from collection, because it's also in the copy ... - collection.erase(middlePosition); -} - -// This removes a random element. -// Note that this leaks if the element happens to be the same -// that's removed above ... -void removeRandom(std::vector& collection) { - auto pos = collection.begin() + time(nullptr) % collection.size(); - - collection.erase(pos); -} - -// Do something with an element. -// Just a dummy function, for you to figure out how to pass an object -// managed by a shared_ptr to a function. -void processElement(const LargeObject* /*element*/) { } - - -// We have pointers to objects in two different collections. We work a bit with -// the collections, and then we try to terminate leak free. Without a shared ownership -// model, this becomes a mess. -void problem3() { - // Let's generate a vector with 10 pointers to LargeObject - std::vector objVector(10); - for (auto& ptr : objVector) { - ptr = new LargeObject(); - } - - // Let's copy it - std::vector objVectorCopy(objVector); - - - // Now we work with the objects: - removeMiddle(objVector); - removeRandom(objVectorCopy); - // ... - // ... - for (auto elm : objVector) { - processElement(elm); - } - - - // Now try to figure out what has to be deleted. It's a mess ... - // Fix using shared_ptr, so the following code becomes unnecessary: - for (auto objPtr : objVector) { - delete objPtr; - } - - for (auto objPtr : objVectorCopy) { - // If the element is in the original collection, it was already deleted. - if (std::find(objVector.begin(), objVector.end(), objPtr) == objVector.end()) { - delete objPtr; - } - } -} - - - -/* -------------------------------------------------------------------------------------------- - * 4: Smart pointers as class members. - * - * Class members that are pointers can quickly become a problem. - * Firstly, if only raw pointers are used, the intended ownership is unclear. - * Secondly, it's easy to overlook that a member has to be deleted. - * Thirdly, pointer members usually require you to implement copy or move constructors and assignment - * operators (--> rule of 3, rule of 5). - * Since C++-11, one can solve a few of those problems using smart pointers. - * - * 4.1: - * The class "Owner" owns some data, but it is broken. If you copy it like in - * problem4_1(), you have two pointers pointing to the same data, but both instances think - * that they own the data. - * - * Tasks: - * - Comment in problem4_1() in main(). - * - It likely crashes. Verify this. You can also try running valgrind ./smartPointers, it should give you some hints as to - * what's happening. - * - Fix the Owner class by using a shared_ptr for its _largeObj, which we can copy as much as we want. - * - Run the fixed program. - * - Note: Once shared_ptr is in use, you can also use the default destructor. - * - * 4.2: **BONUS** - * Let's use a weak_ptr now to observe a shared_ptr. - * These are used to observe a shared_ptr, but unlike the shared_ptr, they don't prevent the deletion - * of the underlying object if all shared_ptr go out of scope. - * To *use* the observed data, one has to create a shared_ptr from the weak_ptr, so that it is guaranteed that - * the underlying object is alive. - * - * In our case, the observer class wants to observe the data of the owner, but it doesn't need to own it. - * To do this, we use a weak pointer. - * - * Tasks: - * - Comment in problem4_2() in main(). - * - Investigate the crash. Use a debugger, run in valgrind, compile with -fsanitize=address ... - * - Rewrite the interface of Owner::getData() such that the observer can see the shared_ptr to the large object. - * - Set up the Observer such that it stores a weak pointer that observes the large object. - * - In Observer::processData(), access the weak pointer, and use the data *only* if the memory is still alive. - * Note: What you need is weak_ptr::lock(). Check out the documentation and the example at the bottom: - * https://en.cppreference.com/w/cpp/memory/weak_ptr/lock - * -------------------------------------------------------------------------------------------- - */ - -class Owner { -public: - Owner() : - _largeObj(new LargeObject()) { } - - ~Owner() { - std::cout << "problem4(): Owner " << this << " is deallocating " << _largeObj << ".\n"; - delete _largeObj; - } - - const LargeObject* getData() const { - return _largeObj; - } - -private: - LargeObject* _largeObj; -}; - - -void problem4_1() { - std::vector owners; - - for (int i=0; i < 5; ++i) { - Owner owner; - owners.push_back(owner); - } - - /* Now we have a problem: - * We created Owner instances on the stack, and copied them into the vector. - * When the instances on the stack are destroyed, the memory is deallocated. - * All copies in the vector now point to the deallocated memory! - * We could fix this using copy constructors (but we don't want to copy the data), - * using move semantics or using shared_ptr. - * Here, we want to go for shared_ptr. - */ -} - - -class Observer { -public: - Observer(const Owner& owner) : - _largeObj(owner.getData()) { } - - double getValue() const { - if (_largeObj) { - return _largeObj->fData[0]; - } - - return -1.; - } - -private: - const LargeObject* _largeObj; // We don't own this. -}; - - - -void problem4_2() { - // We directly construct 5 owners inside the vector to get around problem4_1: - std::vector owners(5); - - // Let's now fill the other vector with observers: - std::vector observers; - observers.reserve(owners.size()); - for (const auto& owner : owners) { - observers.emplace_back(owner); - } - - // Now let's destroy a few of the data owners: - owners.resize(3); - - std::cout << "Values of the observers:\n\t"; - for (const auto& observer : observers) { - // Problem: We don't know if the data is alive ... - // TODO: Fix Observer! - // std::cout << observer.getValue() << " "; - } - std::cout << "\n"; -} - - -int main() { - problem1(); - // problem2(); - // problem3(); - // problem4_1(); - // problem4_2(); -} diff --git a/code/smartPointers/solution/smartPointers.sol.cpp b/code/smartPointers/solution/smartPointers.sol.cpp deleted file mode 100644 index 12b3f1f7..00000000 --- a/code/smartPointers/solution/smartPointers.sol.cpp +++ /dev/null @@ -1,314 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/* - * Please fix all memory leaks / ownership problems using smart pointers. - * (Verify by running the program with valgrind!) - * - * In the *essentials course*: - * - Work on problem1() and problem2() - * - You can have a look at problem3() if interested - * - * In the *advanced course*: - * - Work on problem1() to problem4() - * - * In main() at the bottom, comment in the different parts as you progress through the exercise. - * - * Remember that: - * - The unique ownership of data is expressed using unique_ptr. - * - "Observer" access without ownership is expressed using raw pointers, references or spans. - * - Shared ownership to data is expressed using shared_ptr. - */ - - -/* -------------------------------------------------------------------------------------------- - * 1: Always use smart pointers instead of new. - * - * A frequent source of leaks is a function that terminates in an unexpected way. - * - * - Fix the leak using a smart pointer. - * - The arguments of sumEntries() don't need to change, as it has only read access. - * -------------------------------------------------------------------------------------------- - */ - -// Note how we are using a span pointing to "double const" to ensure that the -// data can only be read. You don't need to change anything in this function. -double sumEntries(std::span range) { - // Simulate an error - throw std::invalid_argument("Error when summing over data."); - - return std::reduce(range.begin(), range.end()); -} - -// Often, data are owned by one entity, and merely used by others. In this case, we hand the data to -// sumEntries() for reading, so the ownership stays with this function. Unfortunately, something goes -// wrong and we didn't use smart pointers. -// Understand and fix the memory leak. -void doStuffWithData() { - auto data = std::make_unique>(); - - sumEntries(*data); -} - - -void problem1() { - try { - doStuffWithData(); - } catch (const std::exception& e) { - std::cerr << "problem1() terminated with exception: \"" << e.what() - << "\" Check for memory leaks.\n"; - } -} - - - -/* -------------------------------------------------------------------------------------------- - * 2: Storing unique_ptr in collections. - * - * Often, one has to store pointers to objects in collections. Fix the memory leaks using unique_ptr. - * - * Notes: - * - Factory functions should return objects either directly or using smart pointers. - * This is good practice, because it clearly shows who owns an object. Fix the return type of the factory function. - * - The vector should own the objects, so try to store them using smart pointers. - * - Since the change function doesn't accept smart pointers, find a solution to pass the objects. - * Note that this works without shared_ptr! - * -------------------------------------------------------------------------------------------- - */ - -// This is a large object. We maybe shouldn't copy it, so using a pointer is advisable to pass it around. -struct LargeObject { - std::array fData; -}; - -// A factory function to create large objects. -std::unique_ptr createLargeObject() { - auto object = std::make_unique(); - // Imagine there is more setup steps of "object" here - // ... - - return object; -} - -// A function to do something with the objects. -// Note that since we don't own the object, we don't need a smart pointer as argument. -void changeLargeObject(LargeObject& object) { - object.fData[0] = 1.; -} - -void problem2() { - std::vector> largeObjects; - - for (unsigned int i=0; i < 10; ++i) { - auto newObj = createLargeObject(); - largeObjects.push_back(std::move(newObj)); // Can only have one copy, so need to "give up" newObj by moving it into the vector. - - // Alternatively: - // largeObject.push_back(createLargeObject()); - } - - for (const auto& obj : largeObjects) { - changeLargeObject(*obj); - } -} - - - -/* -------------------------------------------------------------------------------------------- - * 3: Shared ownership. - * - * Most of the time, ownership can be solved by having one owner (with unique_ptr) and one or - * more observers (raw pointers or references). Sometimes, we need to truly share data, though. - * - * Here is an example of a completely messed up ownership model. It leaks about 1/10 of the times - * it is invoked. - * - Verify this by running it in a loop using a command like: - * while true; do valgrind --leak-check=full --track-origins=yes ./smartPointers 2>&1 | grep -B 5 -A 5 problem3 && exit 1; done - * - Fix the ownership model using shared_ptr! - * - Convert the vectors to holding shared_ptr. - * - Fix the arguments of the functions. - * - Speed optimisation: - * Make sure that you don't create & destroy a shared_ptr in the for loop in problem3() and when calling processElement(). - * -------------------------------------------------------------------------------------------- - */ - -// This removes the element in the middle of the vector. -void removeMiddle(std::vector>& collection) { - auto middlePosition = collection.begin() + collection.size()/2; - - // Must not delete element when erasing from collection, because it's also in the copy ... - collection.erase(middlePosition); -} - -// This removes a random element. -// Note that this leaks if the element happens to be the same -// that's removed above ... -void removeRandom(std::vector>& collection) { - auto pos = collection.begin() + time(nullptr) % collection.size(); - - collection.erase(pos); -} - -// Do something with an element. -// Just a dummy function, for you to figure out how to pass an object -// managed by a shared_ptr to a function. -void processElement(const LargeObject* /*element*/) { } - - -// We have pointers to objects in two different collections. We work a bit with -// the collections, and then we try to terminate leak free. Without a shared ownership -// model, this becomes a mess. -void problem3() { - // Let's generate a vector with 10 pointers to LargeObject - std::vector> objVector; - objVector.reserve(10); - for (unsigned int i = 0; i < 10; i++) { - objVector.emplace_back(new LargeObject()); - } - - // Let's copy it - std::vector> objVectorCopy(objVector); - - - // Now we work with the objects: - removeMiddle(objVector); - removeRandom(objVectorCopy); - // ... - // ... - for (const auto& elm : objVector) { - processElement(elm.get()); - } - - // Destruction happens automatically! -} - - - -/* -------------------------------------------------------------------------------------------- - * 4: Smart pointers as class members. - * - * Class members that are pointers can quickly become a problem. - * Firstly, if only raw pointers are used, the intended ownership is unclear. - * Secondly, it's easy to overlook that a member has to be deleted. - * Thirdly, pointer members usually require you to implement copy or move constructors and assignment - * operators (--> rule of 3, rule of 5). - * Since C++-11, one can solve a few of those problems using smart pointers. - * - * 4.1: - * The class "Owner" owns some data, but it is broken. If you copy it like in - * problem4_1(), you have two pointers pointing to the same data, but both instances think - * that they own the data. - * - * Tasks: - * - Comment in problem4_1() in main(). - * - It likely crashes. Verify this. You can also try running valgrind ./smartPointers, it should give you some hints as to - * what's happening. - * - Fix the Owner class by using a shared_ptr for its _largeObj, which we can copy as much as we want. - * - Run the fixed program. - * - Note: Once shared_ptr is in use, you can also use the default destructor. - * - * 4.2: **BONUS** - * Let's use a weak_ptr now to observe a shared_ptr. - * These are used to observe a shared_ptr, but unlike the shared_ptr, they don't prevent the deletion - * of the underlying object if all shared_ptr go out of scope. - * To *use* the observed data, one has to create a shared_ptr from the weak_ptr, so that it is guaranteed that - * the underlying object is alive. - * - * In our case, the observer class wants to observe the data of the owner, but it doesn't need to own it. - * To do this, we use a weak pointer. - * - * Tasks: - * - Comment in problem4_2() in main(). - * - Investigate the crash. Use a debugger, run in valgrind, compile with -fsanitize=address ... - * - Rewrite the interface of Owner::getData() such that the observer can see the shared_ptr to the large object. - * - Set up the Observer such that it stores a weak pointer that observes the large object. - * - In Observer::processData(), access the weak pointer, and use the data *only* if the memory is still alive. - * Note: What you need is weak_ptr::lock(). Check out the documentation and the example at the bottom: - * https://en.cppreference.com/w/cpp/memory/weak_ptr/lock - * -------------------------------------------------------------------------------------------- - */ - -class Owner { -public: - Owner() : - _largeObj(new LargeObject()) { } - - std::shared_ptr getData() const { - return _largeObj; - } - -private: - std::shared_ptr _largeObj; -}; - - -void problem4_1() { - std::vector owners; - - for (int i=0; i < 5; ++i) { - Owner owner; - owners.push_back(owner); - } - - // No problem now. Every object is deallocated only once. -} - - -class Observer { -public: - Observer(const Owner& owner) : - _largeObj(owner.getData()) { } - - double getValue() const { - if (auto data = _largeObj.lock()) { - return data->fData[0]; - } - - return -1.; - } - -private: - std::weak_ptr _largeObj; // We don't own this. -}; - - - -void problem4_2() { - // We directly construct 5 owners inside the vector to get around problem4_1: - std::vector owners(5); - - // Let's now fill the other vector with observers: - std::vector observers; - observers.reserve(owners.size()); - for (const auto& owner : owners) { - observers.emplace_back(owner); - } - - // Now let's destroy a few of the data owners: - owners.resize(3); - - std::cout << "Values of the observers:\n\t"; - for (const auto& observer : observers) { - std::cout << observer.getValue() << " "; - } - std::cout << "\n"; -} - - -int main() { - problem1(); - problem2(); - problem3(); - problem4_1(); - problem4_2(); -} diff --git a/code/templates/README.md b/code/templates/README.md deleted file mode 100644 index c154364f..00000000 --- a/code/templates/README.md +++ /dev/null @@ -1,17 +0,0 @@ - -## Instructions - -Beginners -* Look at the `OrderedVector` code -* Compile and run `playwithsort.cpp`. See the ordering -* Modify `playwithsort.cpp` and reuse `OrderedVector` with `Complex` - -Intermediary -* Extend `OrderedVector` to allow to customize the ordering via an additional template parameter. - By default, `std::less` should be used. -* Test ordering by the reversed strings (from the last letter, don't change the strings!) -* Test order based on [Manhattan distance](https://en.wikipedia.org/wiki/Taxicab_geometry) with complex type - -Bonus -* Check the implementation of `Complex` -* Try ordering complex of complex diff --git a/code/valgrind/Makefile b/code/valgrind/Makefile deleted file mode 100644 index d544eadb..00000000 --- a/code/valgrind/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -all: randomize -solution: randomize.sol - -clean: - rm -f *o randomize *~ randomize.sol core - -randomize : randomize.cpp - ${CXX} -std=c++17 -g -O0 -Wall -Wextra -L. -o $@ $< - -randomize.sol : solution/randomize.sol.cpp - ${CXX} -std=c++17 -g -O0 -Wall -Wextra -L. -o $@ $< diff --git a/code/valgrind/README.md b/code/valgrind/README.md deleted file mode 100644 index 991367c1..00000000 --- a/code/valgrind/README.md +++ /dev/null @@ -1,6 +0,0 @@ - -## Instructions - -* compile, run, it should work -* run with valgrind (`valgrind ./randomize`) -* fix the problem diff --git a/code/valgrind/randomize.cpp b/code/valgrind/randomize.cpp deleted file mode 100644 index ff3e44f6..00000000 --- a/code/valgrind/randomize.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include -#include - -constexpr auto LEN = 1000; -constexpr auto STEP = 7; - -void swap(int *a, int*b) { - int c = *a; - *a = *b; - *b = c; -} - -void randomize(int* v, unsigned int len) { - std::default_random_engine e; - std::uniform_int_distribution d{0u, len}; - // we randomize via len random inversions - for (unsigned int i = 0; i < len; i++) { - int a = d(e); - int b = d(e); - swap(v+a, v+b); - } -} - -void createAndFillVector(int** v, unsigned int len) { - *v = new int[len]; - for (unsigned int i = 0; i < len; i++) (*v)[i] = i*STEP; -} - -int main() { - int *v; - // create and randomize vector - createAndFillVector(&v, LEN+1); - randomize(v, LEN+1); - - // compute diffs - int *diffs = new int[LEN]; - for (unsigned int i = 0; i < LEN; i++) - diffs[i] = v[i+1] - v[i]; - delete[] v; - delete[] diffs; - - // compute standard deviation of it - float sum = 0; - float sumsq = 0; - for (unsigned int i = 0; i < LEN; i ++) { - sum += diffs[i]; - sumsq += diffs[i]*diffs[i]; - } - float mean = sum/LEN; - float stddev = std::sqrt(sumsq/LEN - mean*mean) ; - std::cout << "Range = [0, " << STEP*LEN << "]\n" - << "Mean = " << mean - << "\nStdDev = " << stddev << '\n'; -} diff --git a/code/valgrind/solution/randomize.sol.cpp b/code/valgrind/solution/randomize.sol.cpp deleted file mode 100644 index 08a3c8ed..00000000 --- a/code/valgrind/solution/randomize.sol.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include -#include - -constexpr auto LEN = 1000; -constexpr auto STEP = 7; - -void swap(int *a, int*b) { - int c = *a; - *a = *b; - *b = c; -} - -void randomize(int* v, unsigned int len) { - std::default_random_engine e; - std::uniform_int_distribution d{0u, len}; - // we randomize via len random inversions - for (unsigned int i = 0; i < len; i++) { - int a = d(e); - int b = d(e); - swap(v+a, v+b); - } -} - -void createAndFillVector(int** v, unsigned int len) { - *v = new int[len]; - for (unsigned int i = 0; i < len; i++) (*v)[i] = i*STEP; -} - -int main() { - int *v; - // create and randomize vector - createAndFillVector(&v, LEN+1); - randomize(v, LEN+1); - - // compute diffs - int *diffs = new int[LEN]; - for (unsigned int i = 0; i < LEN; i++) - diffs[i] = v[i+1] - v[i]; - delete[] v; - - // compute standard deviation of it - float sum = 0; - float sumsq = 0; - for (unsigned int i = 0; i < LEN; i ++) { - sum += diffs[i]; - sumsq += diffs[i]*diffs[i]; - } - delete[] diffs; - - float mean = sum/LEN; - float stddev = std::sqrt(sumsq/LEN - mean*mean) ; - std::cout << "Range = [0, " << STEP*LEN << "]\n" - << "Mean = " << mean - << "\nStdDev = " << stddev << '\n'; -} diff --git a/exercises/.clang-format b/exercises/.clang-format new file mode 100644 index 00000000..182c5b70 --- /dev/null +++ b/exercises/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: WebKit +InsertNewlineAtEOF: True +Standard: Latest diff --git a/exercises/.gitignore b/exercises/.gitignore new file mode 100644 index 00000000..b624e076 --- /dev/null +++ b/exercises/.gitignore @@ -0,0 +1,11 @@ +# generic files and directories +build +*.a +*.o +*.so + +# tools +CMakeCache.txt +CMakeFiles +cmake_install.cmake +callgrind.out.* diff --git a/code/CMakeLists.txt b/exercises/CMakeLists.txt similarity index 97% rename from code/CMakeLists.txt rename to exercises/CMakeLists.txt index bfb2cb5b..6438cc2d 100644 --- a/code/CMakeLists.txt +++ b/exercises/CMakeLists.txt @@ -13,9 +13,8 @@ if( "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}" ) endif() # Include the exercises that (should) work on all platforms. -add_subdirectory( hello ) add_subdirectory( asan ) -add_subdirectory( atomic ) +add_subdirectory( basicTypes ) add_subdirectory( callgrind ) add_subdirectory( condition_variable ) add_subdirectory( constness ) @@ -23,6 +22,7 @@ add_subdirectory( control ) add_subdirectory( cppcheck ) add_subdirectory( debug ) add_subdirectory( functions ) +add_subdirectory( hello ) add_subdirectory( loopsRefsAuto ) add_subdirectory( memcheck ) add_subdirectory( modern_oo ) diff --git a/code/ExerciseSchedule_AdvancedCourse.md b/exercises/ExerciseSchedule_AdvancedCourse.md similarity index 88% rename from code/ExerciseSchedule_AdvancedCourse.md rename to exercises/ExerciseSchedule_AdvancedCourse.md index bd0c98cd..c4e31398 100644 --- a/code/ExerciseSchedule_AdvancedCourse.md +++ b/exercises/ExerciseSchedule_AdvancedCourse.md @@ -5,7 +5,7 @@ HEP C++ Advanced Course's exercise schedule - Solutions and hints can be found in the ExercisesCheatSheet.md file located in the same directory. - Each exercise is in its own directory and referred to in the following by the name of the directory -There are far too many exercies on each day. They are given in order in which they should be done. +There are far too many exercises on each day. They are given in order in which they should be done. Day 1 ----- @@ -36,13 +36,15 @@ People should replay them and discover the tools by themselves. ### (optional) Address sanitizer (directory: [`asan`](asan), [cheatSheet](ExercisesCheatSheet.md#address-sanitizer-directory-asan)) +### (optional) Undefined behaviour sanitizer (directory: [`ubsan`](ubsan) + Day 3 ----- ### Race conditions (directory: [`race`](race), [cheatSheet](ExercisesCheatSheet.md#race-conditions-directory-race)) -### Atomicity (directory: [`atomic`](atomic), [cheatSheet](ExercisesCheatSheet.md#atomicity-directory-atomic)) +### Condition variables (directory: [`condition_variable`](condition_variable), [cheatSheet](ExercisesCheatSheet.md#condition-variables-directory-condition_variable)) ### Generic programming / templates (directory: [`templates`](templates), [cheatSheet](ExercisesCheatSheet.md#generic-programming--templates-directory-templates)) As a prerequisite for variadic templates, and in case it was not covered in day 2 session diff --git a/code/ExerciseSchedule_EssentialCourse.md b/exercises/ExerciseSchedule_EssentialCourse.md similarity index 91% rename from code/ExerciseSchedule_EssentialCourse.md rename to exercises/ExerciseSchedule_EssentialCourse.md index 548418f0..4eed3641 100644 --- a/code/ExerciseSchedule_EssentialCourse.md +++ b/exercises/ExerciseSchedule_EssentialCourse.md @@ -12,6 +12,8 @@ Day 1 - Basics Exercises ### Hello World (directory: [`hello`](hello), [CheatSheet](ExercisesCheatSheet.md#hello-world-directory-hello)) +### Basic types (directory: [`basicTypes`](basicTypes), [CheatSheet](ExercisesCheatSheet.md#basic-types-directory-basictypes)) + ### Functions (directory: [`functions`](functions), [CheatSheet](ExercisesCheatSheet.md#functions-directory-functions)) ### Control Structures (directory: [`control`](control), [CheatSheet](ExercisesCheatSheet.md#control-structures-directory-control)) @@ -35,10 +37,12 @@ People should replay them and discover the tools by themselves. Day 2 - OO Exercises -------------------- -### Polymorphism (directory: [`polymorphism`](polymorphism), [CheatSheet](ExercisesCheatSheet.md#polymorphism-directory-polymorphism)) +### My first classes (directory: [`classes`](classes), [CheatSheet](ExercisesCheatSheet.md#classes-directory-classes)) ### Operator overloading (directory: [`operators`](operators), [CheatSheet](ExercisesCheatSheet.md#operator-overloading-directory-operators)) +### Polymorphism (directory: [`polymorphism`](polymorphism), [CheatSheet](ExercisesCheatSheet.md#polymorphism-directory-polymorphism)) + ### Virtual inheritance (directory: [`virtual_inheritance`](virtual_inheritance), [CheatSheet](ExercisesCheatSheet.md#virtual-inheritance-directory-virtual_inheritance)) diff --git a/code/ExercisesCheatSheet.md b/exercises/ExercisesCheatSheet.md similarity index 88% rename from code/ExercisesCheatSheet.md rename to exercises/ExercisesCheatSheet.md index d494f8da..702d8874 100644 --- a/code/ExercisesCheatSheet.md +++ b/exercises/ExercisesCheatSheet.md @@ -12,6 +12,10 @@ Basics Exercises Just try to compile and run `./hello` to make sure that everything is set up correctly. +### Basic types (directory: [`basicTypes`](basicTypes)) + +The goal is to observe the behaviour of a few basic types, correctly employ integer and floating-point literals, and to be aware of conversions and the effect of operators in expressions with such types. + ### Functions (directory: [`functions`](functions)) pass by copy / pass by reference @@ -41,6 +45,37 @@ The idea of this exercise is to play with all kinds of possible loops and contro Object-orientation Exercises ---------------------------- +### Classes (directory: [`classes`](classes)) + +This exercise is about doing a basic class, not using any operator overloading. It also paves the way for object-functions, making a first algo-like class. + +You may discuss with the students: +- pros and cons of making the constructor explicit or not, +- pros and cons of having multiply() and equal() either as member functions, + friend free functions or external free functions. +- optionally, thed difference between equality and equivalence. + +NOTE: I did not find a justified need to add operator=... + +### Operator overloading (directory: [`operators`](operators)) + +Here we take the class from the previous exercise, and we try to make +anything an operator that makes sense. + +Tips and tricks: +- When the argument of CHECK() has a comma not between parentheses, for example + curly braces, add an additional global level of parenthesis, or CHECK() will + think it has several arguments. + +You may discuss with the students: +- the chaining of operator<< +- in a set of consistent operators (such as == and !=), + reusing versus performance. +- object-functions. +- for what concerns the related operators, such as * and *=, + the choice to be made consistent reuse or performant + specialization of each operator. + ### Polymorphism (directory: [`polymorphism`](polymorphism)) First create a Pentagon and an Hexagon and call computePerimeter. Can be used to break the ice. @@ -75,22 +110,6 @@ See and solve the compilation issue about missing Drawable constructor. Understa See the new id being printed twice. -### Operator overloading (directory: [`operators`](operators)) - -This exercise is about making `main` run successfully by completing the implementation of `Fraction`. -Implement a constructor for `Fraction` and add two integer data members for numerator and denominator. -Comment out everything in `main` except the first two LOCs. -This should compile now and print nothing. - -Then uncomment the `std::cout` statements and implement `operator<<` for `Fraction`. -Compile and run. - -Proceed this way through the entire exercise. -There are multiple possibilities to implement some operators, e.g. as members, as hidden friends, or as free functions. -Also when and where to normalize a fraction is up to the students. -All solutions are fine, as long as the `main` function runs successfully. - - Modern C++ Exercises -------------------- @@ -107,11 +126,11 @@ See in valgrind the improvements. ### Generic programming / templates (directory: [`templates`](templates)) -This exercise has several levels. People not at ease can stop after first level and go to next exercise. Alternatively, they may do level 1 and 3 and skip 2. +This exercise has several levels. People not at ease can stop after the first level and go to next exercise. Alternatively, they may do level 1 and 3 and skip 2. -Level 1 : just use the given Complex class in OrderedVector and see it works out of the box thanks to generic code in OrderedVector. +Level 1: just use the given `Complex` alias (`Complex_t`) inside an `OrderedVector`, fill and print it. See that it works out of the box thanks to generic code in `OrderedVector`. -Level 2 : add a template argument for the ordering in OrderedVector. +Level 2: add a template argument for the ordering in OrderedVector. The idea is to add an extra template argument "Compare" that is a functor comparing 2 arguments and an extra member "m_compare" of type "Compare" to the OrderedVector class. Then the comparison in the add function can be replaced by ```cpp @@ -133,21 +152,24 @@ The goal is to use STL algorithms. I would advise to start in this order : ### Smart pointers (directory: [`smartPointers`](smartPointers)) -Here we have four code snippets that will benefit from using smart pointers. +Here we have five code snippets that will benefit from using smart pointers. **Essentials**: Work on part 1 and 2 **Advanced**: Try all parts - `problem1` is a simple case of usage of `make_unique` with an observer pattern where the raw pointer should be used. - `problem2` is an example of a collection of pointers. Move semantic has to be used to transfer ownership of newly created objects to the container (alternatively, `emplace_back`). - `problem3` is an example of shared ownership where `std::shared_pointer` should be used. -- `problem4` demonstrates the usage of `shared_ptr` as class members. It has a second part where a `weak_ptr` can be used, but can be skipped if not enough time. +- `problem4` demonstrates the usage of `shared_ptr` as class members. +- `problem5` demonstrates the usage of `weak_ptr` can be used, but can be skipped if not enough time. ### std::optional (directory: [`optional`](optional)) + Use std::optional to signify disallowed values in a computation. 1. Use std::optional as return value of the mysqrt function. Use `nullopt_t` for negative arguments. Note that `return {}` will create a `nullopt_t` automatically. 2. Given that the return type changes, modify the square function accordingly to take into account cases where a `nullopt_t` is given as input. Note that `std::optional` can be directly used in an if statement as if it would be a boolean to check whether is value is present ### std::variant (directory: [`variant`](variant)) + Use the variant as an alternative to inheritance. The goal is to understand 1. That the base class is unnecessary when variant is used 2. That no dynamic allocations and polymorphism are necessary because the variant can directly be pushed into the vector @@ -247,7 +269,7 @@ Steps: 3. Inspect changes, try `git diff .` 4. Revert changes using `git checkout -- ` or `git checkout .` 5. You can repeat this with various other styles if you want. Supported are: LLVM, Google, Chromium, Mozilla, WebKit, Microsoft. -6. Go to `code` directory and create a `.clang-format` file: +6. Go to `exercises` directory and create a `.clang-format` file: `clang-format -style=LLVM -dump-config > .clang-format`. Have a look at the file. You can also show this webpage so students can get an idea of what's possible: https://clang.llvm.org/docs/ClangFormatStyleOptions.html @@ -337,13 +359,9 @@ Concurrency Exercises Typical race condition where a simple mutex and lock_guard "solves" the problem. -The second step is to look at the execution time and find out that it's not really a solution. One could then try an atomic and see the difference, although I do not introduce them in the course - -### Atomicity (directory: [`atomic`](atomic)) +The second step is to look at the execution time and find out that it's not really a solution. -Exactly the same race condition as above. Fix them using an `atomic`. - -*Optional*: Compare run times for lock and atomic solution. Those are likely not very different, as many locks are implemented using atomics. +Try then to use an `atomic` to solve the issue and compare the execution time with the lock solution. ### Condition variables (directory: [`condition_variable`](condition_variable)) @@ -351,6 +369,7 @@ Small example where 4 consumer threads are notified by a producer. 1. The production phase is not protected by a lock. 2. When the consumers are waking up, they don't release the lock that's tied to the condition variable, so they cannot wake up in parallel. + Python Exercises ---------------- diff --git a/exercises/Makefile b/exercises/Makefile new file mode 100644 index 00000000..5c7e7613 --- /dev/null +++ b/exercises/Makefile @@ -0,0 +1,21 @@ +TESTDIRS = asan callgrind condition_variable control cppcheck debug functions \ + header_units helgrind hello loopsRefsAuto memcheck modern_oo modules \ + move operators polymorphism python race smartPointers templates \ + valgrind virtual_inheritance +NOCOMPILETESTDIRS = basicTypes constness stl variadic + +all: + for dir in ${TESTDIRS}; do \ + cd $${dir}; \ + make $@; \ + cd ..; \ + done + +solution clean: + for dir in ${TESTDIRS} ${NOCOMPILETESTDIRS}; do \ + cd $${dir}; \ + make $@; \ + cd ..; \ + done + +clobber: clean diff --git a/code/README.md b/exercises/README.md similarity index 95% rename from code/README.md rename to exercises/README.md index 19be3d32..160793fe 100644 --- a/code/README.md +++ b/exercises/README.md @@ -14,7 +14,7 @@ Getting the code ```bash git clone https://github.com/hsf-training/cpluspluscourse.git -cd cpluspluscourse/code +cd cpluspluscourse/exercises ``` Recommended setup @@ -33,12 +33,14 @@ Recommended setup - `valgrind`, `kcachegrind`, `cppcheck`, `clang-format` and `clang-tidy` for corresponding exercises - `g++` or `clang++` as C++ compiler for sanitizer exercises -### C++ and python specific needs +### More setup for the advanced course + +#### C++ and python specific needs - `python3`, `libpython3-dev` - `ctypes`, `matplotlib`, `numpy` python packages -### A word on timing +#### A word on timing - several exercises ask you to "time" things - here it's always sufficient to use the `time` command line: @@ -54,13 +56,14 @@ Recommended setup How to test your setup ---------------------- - **Please run [`check_setup.sh`](check_setup.sh) to check your setup on Linux / Mac.** - - go to [`code/hello`](hello) + The optional tools are not required for the essentials course. + - go to [`exercises/hello`](hello) - follow the `README.md` ### How to compile and run? ```bash -cd code/hello +cd exercises/hello make export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. ./hello @@ -80,7 +83,7 @@ Each exercise is in a subdirectory with mainly 4 sets of files ### Instructions - Each exercise comes with a set of instructions in the course slides - - Most of them also with a `README.md` in the exercise subdir + - Most of them also with a `README.md` in the `exercises` subdir - And with instructions in the code ### *.hpp and *.cpp files @@ -117,7 +120,7 @@ Using lxplus ssh lxplus9.cern.ch export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:. git clone https://github.com/hsf-training/cpluspluscourse.git -cd cpluspluscourse/code/hello +cd cpluspluscourse/exercises/hello make ./hello ``` @@ -250,7 +253,7 @@ There are two ways to launch VS Code for working with WSL: Use the newly opened terminal to type commands for building, debugging, etc. 2. Terminal first: Launch `bash` or `wsl` either via a powershell or from Windows Terminal (if installed). Navigate to your cloned GitHub repository, or `git clone` it if you have not done it yet. - Inside the `/code` or any `/code/example` directory, run `code .`. + Inside the `/exercises` or any `/exercises/example` directory, run `code .`. This will launch VS Code on Windows, already connected to your WSL and set to the right folder. Use your initial terminal to type commands for building, debugging etc. diff --git a/exercises/asan/.gitignore b/exercises/asan/.gitignore new file mode 100644 index 00000000..2283c96d --- /dev/null +++ b/exercises/asan/.gitignore @@ -0,0 +1,2 @@ +asan +asan.sol diff --git a/code/asan/CMakeLists.txt b/exercises/asan/CMakeLists.txt similarity index 100% rename from code/asan/CMakeLists.txt rename to exercises/asan/CMakeLists.txt diff --git a/code/asan/Makefile b/exercises/asan/Makefile similarity index 100% rename from code/asan/Makefile rename to exercises/asan/Makefile diff --git a/code/asan/README.md b/exercises/asan/README.md similarity index 100% rename from code/asan/README.md rename to exercises/asan/README.md diff --git a/code/asan/asan.cpp b/exercises/asan/asan.cpp similarity index 100% rename from code/asan/asan.cpp rename to exercises/asan/asan.cpp diff --git a/code/asan/solution/asan.sol.cpp b/exercises/asan/solution/asan.sol.cpp similarity index 100% rename from code/asan/solution/asan.sol.cpp rename to exercises/asan/solution/asan.sol.cpp diff --git a/exercises/basicTypes/.gitignore b/exercises/basicTypes/.gitignore new file mode 100644 index 00000000..c8d7d09f --- /dev/null +++ b/exercises/basicTypes/.gitignore @@ -0,0 +1,2 @@ +basicTypes +basicTypes.sol diff --git a/exercises/basicTypes/CMakeLists.txt b/exercises/basicTypes/CMakeLists.txt new file mode 100644 index 00000000..16b2d5c8 --- /dev/null +++ b/exercises/basicTypes/CMakeLists.txt @@ -0,0 +1,15 @@ +# Set up the project. +cmake_minimum_required( VERSION 3.12 ) +project( basicTypes LANGUAGES CXX ) + +# Set up the compilation environment. +include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) +set( CMAKE_CXX_STANDARD 20 ) + +# Create the user's executable. +add_executable( basicTypes PrintHelper.h basicTypes.cpp ) + +# Create the "solution executable". +add_executable( basicTypes.sol EXCLUDE_FROM_ALL PrintHelper.h solution/basicTypes.sol.cpp ) +target_include_directories( basicTypes.sol PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) +add_dependencies( solution basicTypes.sol ) diff --git a/exercises/basicTypes/Makefile b/exercises/basicTypes/Makefile new file mode 100644 index 00000000..285197b9 --- /dev/null +++ b/exercises/basicTypes/Makefile @@ -0,0 +1,11 @@ +all: basicTypes +solution: basicTypes.sol + +clean: + rm -f *o *so basicTypes *~ basicTypes.sol + +% : %.cpp PrintHelper.h + $(CXX) -g -std=c++20 -Wall -Wextra -o $@ $< + +%.sol : solution/%.sol.cpp PrintHelper.h + $(CXX) -g -std=c++20 -Wall -Wextra -o $@ $< -I . diff --git a/exercises/basicTypes/PrintHelper.h b/exercises/basicTypes/PrintHelper.h new file mode 100644 index 00000000..7df4c126 --- /dev/null +++ b/exercises/basicTypes/PrintHelper.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include + +/* + * NOTE: You don't need to understand the print helpers here. + * Their purpose is to show each expression, the type it evaluates to, and the value it evaluates to. + * Please go back to the main file now. :-) + */ + +#ifdef _MSC_VER +std::string demangle(std::string_view input) { return std::string{input}; } +#else +#include +std::string demangle(std::string_view input) { + int status; + return abi::__cxa_demangle(input.data(), NULL, NULL, &status); +} +#endif + +// This helper prints type and value of an expression +void printWithTypeInfo(std::string expression, auto const & t, bool useBitset = false) { + const auto & ti = typeid(t); + const std::string realname = demangle(ti.name()); + + std::cout << std::left << std::setw(30) << expression << " type=" << std::setw(20) << realname << "value="; + if (useBitset) { + std::cout << "0b" << std::bitset<16>(t) << "\n"; + } else { + std::cout << std::setprecision(25) << t << "\n"; + } +} + +// This macro both prints and evaluates an expression: +#define print(A) printWithTypeInfo("Line " + std::to_string(__LINE__) + ": "#A, A); +#define printBinary(A) printWithTypeInfo("Line " + std::to_string(__LINE__) + ": "#A, A, true); diff --git a/exercises/basicTypes/README.md b/exercises/basicTypes/README.md new file mode 100644 index 00000000..132a0a6e --- /dev/null +++ b/exercises/basicTypes/README.md @@ -0,0 +1,7 @@ +# Fundamental types and expressions + +## Tasks: +- Compile the program and analyse the output of the different expressions +- Discuss with other students or your tutor in case the result of an expression is a surprise +- Fix the marked expressions by changing types such that they produce meaningful results +- Answer the questions in the code diff --git a/exercises/basicTypes/basicTypes.cpp b/exercises/basicTypes/basicTypes.cpp new file mode 100644 index 00000000..b67bccb8 --- /dev/null +++ b/exercises/basicTypes/basicTypes.cpp @@ -0,0 +1,79 @@ +#include "PrintHelper.h" + +/* ************************************* + * * Fundamental types and expressions * + * ************************************* + * + * Tasks: + * ------ + * - Compile the program and analyse the output of the different expressions + * - Discuss with other students or your tutor in case the result of an expression is a surprise + * - Fix the marked expressions by changing types such that they produce meaningful results + * - Answer the questions in the code + */ + +int main() { + std::cout << "Using literals of different number types:\n"; + print(5); + print(5/2); //FIXME + print(100/2ull); + print(2 + 4ull); + print(2.f + 4ull); + print(0u - 1u); // FIXME + print(1.0000000001f); // FIXME Why is this number not represented correctly? + print(1. + 1.E-18); // FIXME + + std::cout << "\nUsing increment and decrement operators:\n"; + int a = 1; + int b; + int c; + print(b = a++); // Q: What is the difference between a++ and ++a? + print(c = ++a); + print(a); + print(b); + print(c); + + std::cout << "\nCompound assignment operators:\n"; + int n = 1; + print(n *= 2); // Q: Is there a difference between this and the next line? + print(n *= 2.9); + print(n -= 1.1f); + print(n /= 4); // Q: Based on the results of these expressions, is there a better type to be used for n? + + std::cout << "\nLogic expressions:\n"; + const bool alwaysTrue = true; + bool condition1 = false; + bool condition2 = true; + print( alwaysTrue && condition1 && condition2 ); + print( alwaysTrue || condition1 && condition2 ); // Q: Are this and the following expressions useful? + print( alwaysTrue && condition1 || condition2 ); + print(condition1 != condition1); // Q: What is the difference between this and the following expression? + print(condition2 = !condition2); + print( alwaysTrue && condition1 && condition2 ); + print( alwaysTrue || condition1 && condition2 ); + print( alwaysTrue && condition1 || condition2 ); + + std::cout << '\n'; + print( false || 0b10 ); // Q: What is the difference between || and | ? + print( false | 0b10 ); + printBinary( 0b1 & 0b10 ); + printBinary( 0b1 | 0b10 ); + printBinary( 0b1 && 0b10 ); // Q: Are the operators && and || appropriate for integer types? + printBinary( 0b1 || 0b10 ); + + std::cout << "\nPlay with characters and strings:\n"; + print("a"); // Q: Why is this expression two bytes at run time, the next only one? + print('a'); + + char charArray[20]; + char* charPtr = charArray; + charArray[19] = 0; // Make sure that our string is terminated with the null byte + + print(charArray); + print(charArray[0] = 'a'); + print(charArray); + print(charArray[1] = 98); + print(charArray); + print(charPtr); + // FIXME: Ensure that no unexpected garbage is printed above +} diff --git a/exercises/basicTypes/solution/basicTypes.sol.cpp b/exercises/basicTypes/solution/basicTypes.sol.cpp new file mode 100644 index 00000000..047c5d89 --- /dev/null +++ b/exercises/basicTypes/solution/basicTypes.sol.cpp @@ -0,0 +1,82 @@ +#include "PrintHelper.h" + +/* ************************************* + * * Fundamental types and expressions * + * ************************************* + * + * Tasks: + * - Compile the program and analyse the output of the different expressions + * - Discuss with other students or your tutor in case the result of an expression is a surprise + * - Fix the marked expressions by changing types such that they produce meaningful results + * - Answer the questions in the code + */ + +int main() { + std::cout << "Using literals of different number types:\n"; + print(5); + print(5/2.); //FIXME + print(100/2ull); + print(2 + 4ull); + print(2.f + 4ull); + print(0 - 1 ); // FIXME + print(1.0000000001 ); // FIXME Why is this number not represented correctly? + print(1.l+ 1.E-18); // FIXME + + std::cout << "\nUsing increment and decrement operators:\n"; + int a = 1; + int b; + int c; + print(b = a++); // Q: What is the difference between a++ and ++a? + print(c = ++a); // A: Whether it returns the previous or new value + print(a); + print(b); + print(c); + + std::cout << "\nCompound assignment operators:\n"; + float n = 1; + print(n *= 2); // Q: Is there a difference between this and the next line? + print(n *= 2.9); // A: Yes, the computation runs in float and is converted back to int + print(n -= 1.1f); + print(n /= 4); // Q: Based on the results of these expressions, is there a better type to be used for n? + // A: Probably yes, for example float + + std::cout << "\nLogic expressions:\n"; + const bool alwaysTrue = true; + bool condition1 = false; + bool condition2 = true; + print( alwaysTrue && condition1 && condition2 ); + print( alwaysTrue || condition1 && condition2 ); // Q: Are this and the following expressions useful? + // A: Not really. Since we use "true ||", it is always true. + print( alwaysTrue && condition1 || condition2 ); // A: "true && condition1" is the same as "condition1" + print(condition1 != condition1); // Q: What is the difference between this and the following expression? + print(condition2 = !condition2); // A: The first is a comparison, the second a negation with subsequent assignment + print( alwaysTrue && condition1 && condition2 ); + print( alwaysTrue || condition1 && condition2 ); + print( alwaysTrue && condition1 || condition2 ); + + std::cout << '\n'; + print( false || 0b10 ); // Q: What is the difference between || and | ? + print( false | 0b10 ); // A: a boolean operation vs. a bit-wise boolean operation + printBinary( 0b1 & 0b10 ); + printBinary( 0b1 | 0b10 ); + printBinary( 0b1 && 0b10 ); // Q: Are the operators && and || appropriate for integer types? + printBinary( 0b1 || 0b10 ); // A: Most likely not, because the integers are first converted to boolean + + std::cout << "\nPlay with characters and strings:\n"; + print("a"); // Q: Why is this expression two bytes at run time, the next only one? + print('a'); // A: Because the first one is a string, which is 0-terminated + + char charArray[20]; + // There are many ways to solve this, for example to use std::string and not manually manage the memory. + // However, if one really desires to manage a char array, one should at least initialise it with the 0 byte: + std::fill(std::begin(charArray), std::end(charArray), '\0'); + char* charPtr = charArray; + + print(charArray); + print(charArray[0] = 'a'); + print(charArray); + print(charArray[1] = 98); + print(charArray); + print(charPtr); + // FIXME: Ensure that no unexpected garbage is printed above +} diff --git a/exercises/callgrind/.gitignore b/exercises/callgrind/.gitignore new file mode 100644 index 00000000..8f9f534d --- /dev/null +++ b/exercises/callgrind/.gitignore @@ -0,0 +1,2 @@ +fibocrunch +fibocrunch.sol diff --git a/code/callgrind/CMakeLists.txt b/exercises/callgrind/CMakeLists.txt similarity index 100% rename from code/callgrind/CMakeLists.txt rename to exercises/callgrind/CMakeLists.txt diff --git a/code/callgrind/Makefile b/exercises/callgrind/Makefile similarity index 100% rename from code/callgrind/Makefile rename to exercises/callgrind/Makefile diff --git a/code/callgrind/README.md b/exercises/callgrind/README.md similarity index 100% rename from code/callgrind/README.md rename to exercises/callgrind/README.md diff --git a/code/callgrind/fibocrunch.cpp b/exercises/callgrind/fibocrunch.cpp similarity index 100% rename from code/callgrind/fibocrunch.cpp rename to exercises/callgrind/fibocrunch.cpp diff --git a/code/callgrind/solution/fibocrunch.sol.cpp b/exercises/callgrind/solution/fibocrunch.sol.cpp similarity index 100% rename from code/callgrind/solution/fibocrunch.sol.cpp rename to exercises/callgrind/solution/fibocrunch.sol.cpp diff --git a/code/check_setup.sh b/exercises/check_setup.sh similarity index 100% rename from code/check_setup.sh rename to exercises/check_setup.sh diff --git a/exercises/classes/.gitignore b/exercises/classes/.gitignore new file mode 100644 index 00000000..a5a19752 --- /dev/null +++ b/exercises/classes/.gitignore @@ -0,0 +1,2 @@ +classes +classes_sol diff --git a/exercises/classes/CMakeLists.txt b/exercises/classes/CMakeLists.txt new file mode 100644 index 00000000..cfc53a9c --- /dev/null +++ b/exercises/classes/CMakeLists.txt @@ -0,0 +1,13 @@ +# Set up the project. +cmake_minimum_required( VERSION 3.12 ) +project( operators LANGUAGES CXX ) + +# Set up the compilation environment. +include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) + +# Create the user's executable. +add_executable( classes "classes.cpp" ) + +# Create the "solution executable". +add_executable( classes_sol EXCLUDE_FROM_ALL "solution/classes_sol.cpp" ) +add_dependencies( solution classes_sol ) diff --git a/exercises/classes/Makefile b/exercises/classes/Makefile new file mode 100644 index 00000000..b0a6014b --- /dev/null +++ b/exercises/classes/Makefile @@ -0,0 +1,11 @@ +all: classes +solution: classes_sol + +clean: + rm -f *o classes *~ classes_sol + +classes : classes.cpp + ${CXX} -g -std=c++17 -O0 -Wall -Wextra -L. -o $@ $< + +classes_sol : solution/classes_sol.cpp + ${CXX} -g -std=c++17 -O0 -Wall -Wextra -L. -o $@ $< diff --git a/exercises/classes/README.md b/exercises/classes/README.md new file mode 100644 index 00000000..26aba0a8 --- /dev/null +++ b/exercises/classes/README.md @@ -0,0 +1,32 @@ + +## Instructions + +STEP 1 +- Complete the class Fraction so that a Fraction can be construct from one or two integer. + Check the first two lines of main() are working (comment out the rest) +- Add the function equal(). + Check the second section of main() works. +- Add the function multiply(). + Check the whole main() works. + +STEP 2 +- Replace the function printTestResult() by a class TestResultPrinter + with a method process() that take the same arguments as before. + Upgrade CHECK() and main(). +- Transform the WIDTH constant into a variable member of TestResultPrinter, + which is initialized in its constructor. + Upgrade main(). + +OPTIONAL STEP 3 +- Move multiply() and compare() as friend functions within the class Fraction. + Check main() works. +- Remove the accessors numerator() and denominator(). + Check main() works. + +OPTIONAL STEP 4 +- Remove the systematic call to normalize(). +- Add a equivalent() method to Fraction. +- Upgrade the tests accordingly. +- Transform the private normalize() into a public const normalized() method + which return the normalized fraction. +- Add some tests to check normalized(). diff --git a/exercises/classes/classes.cpp b/exercises/classes/classes.cpp new file mode 100644 index 00000000..05bd5dc4 --- /dev/null +++ b/exercises/classes/classes.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include + +class Fraction { + +public: + + // ADD YOUR CODE HERE + + std::string str() const { + std::ostringstream oss; + oss << m_num << '/' << m_denom; + return oss.str(); + } + + int numerator() const { + return m_num; + } + int denominator() const { + return m_denom; + } + +private: + + void normalize() { + const int gcd = std::gcd(m_num, m_denom); + m_num /= gcd; + m_denom /= gcd; + } + + int m_num, m_denom; +}; + +// ADD YOUR CODE HERE + +// This is using the cpp, the C preprocessor to expand a bit of code +// (the what argument) to a pair containing a string representation +// of it and the code itself. That way, print is given a string and a +// value where the string is the code that lead to the value +#define CHECK(print,what) print(#what, what) + +unsigned int WIDTH {20}; + +void printTestResult(std::string const & what, bool passed) { + std::cout << std::setw(WIDTH) << what << ": " << (passed ? "PASS" : "** FAIL **") << '\n'; +} + +int main() { + + // create a fraction with values 3 (which is 3/1) and 1/3 + std::cout< +#include +#include +#include + +class Fraction { + +public: + + Fraction(int a_num, int a_denom = 1) : m_num(a_num), m_denom(a_denom) {} + + std::string str() const { + std::ostringstream oss; + oss << m_num << '/' << m_denom; + return oss.str(); + } + + friend bool equal( Fraction const & lhs, Fraction const & rhs ) { + return (lhs.m_num==rhs.m_num) && (lhs.m_denom==rhs.m_denom); + } + + friend bool equivalent( Fraction const & lhs, Fraction const & rhs ) { + return (lhs.m_num*rhs.m_denom==rhs.m_num*lhs.m_denom); + } + + friend Fraction multiply( Fraction const & lhs, Fraction const & rhs ) { + return {lhs.m_num*rhs.m_num, lhs.m_denom*rhs.m_denom}; + } + + Fraction normalized() const { + const int gcd = std::gcd(m_num, m_denom); + return {m_num/gcd, m_denom/gcd}; + } + +private: + + int m_num, m_denom; +}; + +class TestResultPrinter { + +public: + + TestResultPrinter( unsigned int a_width ) : m_width(a_width) {} + + void process(std::string const & what, bool passed) { + std::cout << std::left << std::setw(m_width) << what << ": " << (passed ? "PASS" : "** FAIL **") << '\n'; + } + +private: + + unsigned int m_width; + +}; + +// This is using the cpp, the C preprocessor to expand a bit of code +// (the what argument) to a pair containing a string representation +// of it and the code itself. That way, print is given a string and a +// value where the string is the code that lead to the value +#define CHECK(printer,what) printer.process(#what, what) + +int main() { + + // create a fraction with values 3 (which is 3/1) and 1/3 + std::cout< +#include + +/* This is a dummy function to demonstrate pass by value. + * Since it doesn't do anything with the argument, we suppress + * possible compiler warnings using `maybe_unused`. + */ +void copy(int a) { + [[maybe_unused]] int val = a; +} + +void copyConst(const int a) { + [[maybe_unused]] int val = a; +} + +void write(int* a) { + *a = 42; +} +void write(int& a) { + a = 42; +} + +void read(const int* a) { + [[maybe_unused]] int val = *a; +} +void read(int const & a) { + [[maybe_unused]] int val = a = 2; +} + +struct Test { + void hello(std::string &s) { + std::cout << "Hello " << s << '\n'; + } + void helloConst(std::string &s) const { + std::cout << "Hello " << s << '\n'; + } +}; + +int main() { + // try pointer to constant + int a = 1, b = 2; + int const *i = &a; + *i = 5; + i = &b; + + // try constant pointer + int * const j = &a; + *j = 5; + j = &b; + + // try constant pointer to constant + int const * const k = &a; + *k = 5; + k = &b; + + // try constant arguments of functions + int l = 0; + const int m = 0; + copy(l); + copy(m); + copyConst(l); + copyConst(m); + + // try constant arguments of functions with pointers + { + int *p = &a; + const int *r = &b; + write(p); + write(r); + read(p); + read(r); + } + + // try constant arguments of functions with references + { + int p = 0; + const int r = 0; + write(2); + write(r); + read(2); + read(r); + } + + // try constant method in a class + Test t; + const Test tc; + std::string s("World"); + t.hello(s); + tc.hello(s); + t.helloConst(s); + tc.helloConst(s); + + return 0; +} diff --git a/exercises/control/.gitignore b/exercises/control/.gitignore new file mode 100644 index 00000000..3c3b9fe9 --- /dev/null +++ b/exercises/control/.gitignore @@ -0,0 +1,2 @@ +control +control.sol diff --git a/code/control/CMakeLists.txt b/exercises/control/CMakeLists.txt similarity index 100% rename from code/control/CMakeLists.txt rename to exercises/control/CMakeLists.txt diff --git a/code/control/Makefile b/exercises/control/Makefile similarity index 100% rename from code/control/Makefile rename to exercises/control/Makefile diff --git a/code/control/README.md b/exercises/control/README.md similarity index 100% rename from code/control/README.md rename to exercises/control/README.md diff --git a/code/control/control.cpp b/exercises/control/control.cpp similarity index 100% rename from code/control/control.cpp rename to exercises/control/control.cpp diff --git a/code/control/solution/control.sol.cpp b/exercises/control/solution/control.sol.cpp similarity index 100% rename from code/control/solution/control.sol.cpp rename to exercises/control/solution/control.sol.cpp diff --git a/exercises/cppcheck/.gitignore b/exercises/cppcheck/.gitignore new file mode 100644 index 00000000..634133d7 --- /dev/null +++ b/exercises/cppcheck/.gitignore @@ -0,0 +1,2 @@ +randomize +randomize.sol diff --git a/code/cppcheck/CMakeLists.txt b/exercises/cppcheck/CMakeLists.txt similarity index 100% rename from code/cppcheck/CMakeLists.txt rename to exercises/cppcheck/CMakeLists.txt diff --git a/code/cppcheck/Makefile b/exercises/cppcheck/Makefile similarity index 100% rename from code/cppcheck/Makefile rename to exercises/cppcheck/Makefile diff --git a/code/cppcheck/README.md b/exercises/cppcheck/README.md similarity index 100% rename from code/cppcheck/README.md rename to exercises/cppcheck/README.md diff --git a/code/cppcheck/randomize.cpp b/exercises/cppcheck/randomize.cpp similarity index 100% rename from code/cppcheck/randomize.cpp rename to exercises/cppcheck/randomize.cpp diff --git a/code/cppcheck/solution/randomize.sol.cpp b/exercises/cppcheck/solution/randomize.sol.cpp similarity index 100% rename from code/cppcheck/solution/randomize.sol.cpp rename to exercises/cppcheck/solution/randomize.sol.cpp diff --git a/exercises/debug/.gitignore b/exercises/debug/.gitignore new file mode 100644 index 00000000..18ed3421 --- /dev/null +++ b/exercises/debug/.gitignore @@ -0,0 +1,2 @@ +debug +debug.sol diff --git a/code/debug/CMakeLists.txt b/exercises/debug/CMakeLists.txt similarity index 58% rename from code/debug/CMakeLists.txt rename to exercises/debug/CMakeLists.txt index 950f6eaf..2f7f0ac1 100644 --- a/code/debug/CMakeLists.txt +++ b/exercises/debug/CMakeLists.txt @@ -6,8 +6,8 @@ project( debug LANGUAGES CXX ) include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) # Create the user's executable. -add_executable( debug_randomize "randomize.cpp" ) +add_executable( debug "debug.cpp" ) # Create the "solution executable". -add_executable( debug_randomize.sol EXCLUDE_FROM_ALL "solution/randomize.sol.cpp" ) -add_dependencies( solution debug_randomize.sol ) +add_executable( debug.sol EXCLUDE_FROM_ALL "solution/debug.sol.cpp" ) +add_dependencies( solution debug.sol ) diff --git a/exercises/debug/Makefile b/exercises/debug/Makefile new file mode 100644 index 00000000..d41fde73 --- /dev/null +++ b/exercises/debug/Makefile @@ -0,0 +1,11 @@ +all: debug +solution: debug.sol + +clean: + rm -f *o debug *~ debug.sol core + +debug : debug.cpp + ${CXX} -std=c++17 -g -O0 -Wall -Wextra -L. -o $@ $< + +debug.sol : solution/debug.sol.cpp + ${CXX} -std=c++17 -g -O0 -Wall -Wextra -L. -o $@ $< diff --git a/exercises/debug/README.md b/exercises/debug/README.md new file mode 100644 index 00000000..4fec057a --- /dev/null +++ b/exercises/debug/README.md @@ -0,0 +1,28 @@ +## Instructions for the "debug" exercise + +* compile, run, see the crash +* run it in gdb +* inspect backtrace, variables to understand the problem +* try stepping through the code using 'step' and 'next', use breakpoints and continue to get familiar with gdb +* fix the bug + +### Some useful gdb commands + +| Command | Effect | +|---------------------------|---------------------------------------------------------------| +| `gdb ./randomize` | launch the debugger with the given executable | +| `gdb --tui ./randomize` | same as above, but with terminal user interface enabled | +| `tui enable` | enabled the terminal user interface | +| `run` | runs the program | +| `bt, backtrace` | show a backtrace (list of stack frames) of the current thread | +| `up` | navigate one stack frame up | +| `down` | navigate one stack frame down | +| `s, step` | execute current line, stop at earliest next occasion | +| `n, next` | continue until next line or function exits | +| `c, continue` | continue execution | +| `print ` | show current value of variable | +| `info locals` | show the values of all local variables | +| `info args` | show the values of all function arguments | +| `br ` | put a breakpoint at line | +| `info br`: | list active breakpoints | +| `enable/disable/delete #` | enable/disable/delete breakpoints | diff --git a/exercises/debug/debug.cpp b/exercises/debug/debug.cpp new file mode 100644 index 00000000..a09da9de --- /dev/null +++ b/exercises/debug/debug.cpp @@ -0,0 +1,44 @@ +#include +#include + +void swap(int* a, int* b) +{ + int c = *a; + *a = *b; + *b = c; +} + +void reverse(int* v, unsigned int len) +{ + for (unsigned int i = 0; i < (len + 1) / 2; i++) { + const int a = i; + const int b = len - 1 - i; + + swap(v + a, v + b); + } +} + +int* createAndFillVector(unsigned int len) +{ + auto v = new int[len]; + for (unsigned int i = 0; i < len; i++) { + v[i] = i; + } + return v; +} + +int main() +{ + constexpr auto arraySize = 100; + int* v = nullptr; + // create and reverse the vector of LEN numbers + reverse(v, 1000); + v = createAndFillVector(arraySize); + + // check if the revert worked: + const bool isReversed = std::is_sorted(v, v + arraySize, std::greater {}); + std::cout << "Vector reversed successfully: " << std::boolalpha + << isReversed << "\n"; + + return isReversed ? 0 : 1; +} diff --git a/exercises/debug/solution/debug.sol.cpp b/exercises/debug/solution/debug.sol.cpp new file mode 100644 index 00000000..5676e620 --- /dev/null +++ b/exercises/debug/solution/debug.sol.cpp @@ -0,0 +1,46 @@ +#include +#include + +void swap(int* a, int* b) +{ + int c = *a; + *a = *b; + *b = c; +} + +void reverse(int* v, unsigned int len) +{ + for (unsigned int i = 0; i < (len + 1) / 2; i++) { + const int a = i; + const int b = len - 1 - i; + + swap(v + a, v + b); + } +} + +int* createAndFillVector(unsigned int len) +{ + auto v = new int[len]; + for (unsigned int i = 0; i < len; i++) { + v[i] = i; + } + return v; +} + +int main() +{ + constexpr auto arraySize = 100; + int* v = nullptr; + // create and reverse the vector of LEN numbers + v = createAndFillVector(arraySize); + reverse(v, arraySize); + + // check if the revert worked: + const bool isReversed = std::is_sorted(v, v + arraySize, std::greater {}); + std::cout << "Vector reversed successfully: " << std::boolalpha + << isReversed << "\n"; + + delete[] v; + + return isReversed ? 0 : 1; +} diff --git a/exercises/functions/.gitignore b/exercises/functions/.gitignore new file mode 100644 index 00000000..ab406cf6 --- /dev/null +++ b/exercises/functions/.gitignore @@ -0,0 +1,2 @@ +functions +functions.sol diff --git a/code/functions/CMakeLists.txt b/exercises/functions/CMakeLists.txt similarity index 100% rename from code/functions/CMakeLists.txt rename to exercises/functions/CMakeLists.txt diff --git a/code/functions/Makefile b/exercises/functions/Makefile similarity index 100% rename from code/functions/Makefile rename to exercises/functions/Makefile diff --git a/code/functions/README.md b/exercises/functions/README.md similarity index 73% rename from code/functions/README.md rename to exercises/functions/README.md index 8bdaba52..17fd9784 100644 --- a/code/functions/README.md +++ b/exercises/functions/README.md @@ -14,7 +14,7 @@ They are exactly what their name says, so let's try to avoid copying the latter. ## Step 2 -Using `printFiveCharacters()` as an example, write a function that prints the first five characters of `SlowToCopy`. Call it in `main()`. +Using `printName()` as an example, write a function that prints the name of `SlowToCopy`. Call it in `main()`. ## Step 3 @@ -22,6 +22,6 @@ Try passing by copy and passing by reference, see the difference. ## Step 4 -When passing by reference, ensure that your `printFiveCharacters` cannot inadvertently modify the original object. -To test its const correctness, try adding something like `argument.name[0] = 'a';` to your print function. +When passing by reference, ensure that your `printName` cannot inadvertently modify the original object. +To test its const correctness, try adding something like `argument.name = "a";` to your print function. Try both with and without const attributes in your print function's signature. diff --git a/code/functions/Structs.cpp b/exercises/functions/Structs.cpp similarity index 83% rename from code/functions/Structs.cpp rename to exercises/functions/Structs.cpp index 979136d9..4f24a9d3 100644 --- a/code/functions/Structs.cpp +++ b/exercises/functions/Structs.cpp @@ -6,7 +6,7 @@ #include /// Construct a new instance of SlowToCopy. -SlowToCopy::SlowToCopy() : name("Large type") {} +SlowToCopy::SlowToCopy() : name("SlowToCopy") {} /// Construct a new instance of SlowToCopy. SlowToCopy::SlowToCopy(const std::string& name) : name(name) {} @@ -16,5 +16,4 @@ SlowToCopy::SlowToCopy(const SlowToCopy& other) { std::cout << __func__ << ": Please don't copy me. This is slow.\n"; std::this_thread::sleep_for(std::chrono::seconds(3)); name = other.name; - std::memcpy(bigdata, other.bigdata, sizeof(bigdata)); } diff --git a/code/functions/Structs.h b/exercises/functions/Structs.h similarity index 92% rename from code/functions/Structs.h rename to exercises/functions/Structs.h index be0c9b38..2497855d 100644 --- a/code/functions/Structs.h +++ b/exercises/functions/Structs.h @@ -8,7 +8,6 @@ struct FastToCopy { struct SlowToCopy { std::string name; - int bigdata[1000000]; // Functions to create and copy this struct. // We go into details on the next days. diff --git a/code/functions/functions.cpp b/exercises/functions/functions.cpp similarity index 57% rename from code/functions/functions.cpp rename to exercises/functions/functions.cpp index eac526db..c532bcb6 100644 --- a/code/functions/functions.cpp +++ b/exercises/functions/functions.cpp @@ -4,12 +4,12 @@ * FastToCopy * SlowToCopy * They are exactly what their name says, so let's try to avoid copying the latter. - * 2. Using "printFiveCharacters()" as an example, write a function that prints the first five characters of "SlowToCopy". + * 2. Using "printName()" as an example, write a function that prints the name of "SlowToCopy". * Call it in main(). * 3. Try passing by copy and passing by reference, see the difference. - * 4. When passing by reference, ensure that your "printFiveCharacters" cannot inadvertently modify the original object. + * 4. When passing by reference, ensure that your "printName" cannot inadvertently modify the original object. * To test its const correctness, try adding something like - * argument.name[0] = 'a'; + * argument.name = "other name"; * to your print function. * Try both with and without const attributes in your print function's signature. */ @@ -18,15 +18,15 @@ #include // For printing -void printFiveCharacters(FastToCopy argument) { - std::cout << argument.name << "\n"; +void printName(FastToCopy argument) { + std::cout << argument.name << '\n'; } int main() { - FastToCopy fast = {"abcdef"}; - printFiveCharacters(fast); + FastToCopy fast = {"Fast"}; + printName(fast); - SlowToCopy slow = {"ghijkl"}; + SlowToCopy slow = {"Slow"}; // print it here return 0; diff --git a/exercises/functions/solution/functions.sol.cpp b/exercises/functions/solution/functions.sol.cpp new file mode 100644 index 00000000..543e8c0c --- /dev/null +++ b/exercises/functions/solution/functions.sol.cpp @@ -0,0 +1,50 @@ + +/* Tasks: + * 1. Check out Structs.h. It defines two structs that we will work with. + * FastToCopy + * SlowToCopy + * They are exactly what their name says, so let's try to avoid copying the latter. + * 2. Using "printName()" as an example, write a function that prints the name of "SlowToCopy". + * Call it in main(). + * 3. Try passing by copy and passing by reference, see the difference. + * 4. When passing by reference, ensure that your "printName" cannot inadvertently modify the original object. + * To test its const correctness, try adding something like + * argument.name = "other name"; + * to your print function. + * Try both with and without const attributes in your print function's signature. + */ + +#include "Structs.h" // The data structs we will work with + +#include // For printing + +void printName(FastToCopy argument) { + std::cout << argument.name << '\n'; +} + +void inefficientPrintName(SlowToCopy argument) { + std::cout << argument.name << '\n'; + + // We can change the argument's name because it's a copy: + argument.name = "New name"; +} + +void printName(const SlowToCopy & argument) { + std::cout << argument.name << '\n'; + + // We are unable to change name, as we should: + //argument.name = '\n'; +} + +int main() { + FastToCopy fast = {"Fast"}; + printName(fast); + + SlowToCopy slow = {"Slow"}; + printName(slow); + + std::cout << "Now printing with copy:\n"; + inefficientPrintName(slow); + + return 0; +} diff --git a/exercises/header_units/.gitignore b/exercises/header_units/.gitignore new file mode 100644 index 00000000..10be76d2 --- /dev/null +++ b/exercises/header_units/.gitignore @@ -0,0 +1,3 @@ +header_units +header_units.sol +gcm.cache diff --git a/code/header_units/Complex.hpp b/exercises/header_units/Complex.hpp similarity index 100% rename from code/header_units/Complex.hpp rename to exercises/header_units/Complex.hpp diff --git a/code/header_units/Makefile b/exercises/header_units/Makefile similarity index 100% rename from code/header_units/Makefile rename to exercises/header_units/Makefile diff --git a/code/header_units/README.md b/exercises/header_units/README.md similarity index 100% rename from code/header_units/README.md rename to exercises/header_units/README.md diff --git a/code/header_units/main.cpp b/exercises/header_units/main.cpp similarity index 100% rename from code/header_units/main.cpp rename to exercises/header_units/main.cpp diff --git a/code/header_units/solution/Makefile b/exercises/header_units/solution/Makefile similarity index 100% rename from code/header_units/solution/Makefile rename to exercises/header_units/solution/Makefile diff --git a/code/header_units/solution/main.cpp b/exercises/header_units/solution/main.cpp similarity index 100% rename from code/header_units/solution/main.cpp rename to exercises/header_units/solution/main.cpp diff --git a/exercises/helgrind/.gitignore b/exercises/helgrind/.gitignore new file mode 100644 index 00000000..c0308138 --- /dev/null +++ b/exercises/helgrind/.gitignore @@ -0,0 +1,2 @@ +fiboMT +fiboMT.sol diff --git a/code/helgrind/CMakeLists.txt b/exercises/helgrind/CMakeLists.txt similarity index 100% rename from code/helgrind/CMakeLists.txt rename to exercises/helgrind/CMakeLists.txt diff --git a/code/helgrind/Makefile b/exercises/helgrind/Makefile similarity index 100% rename from code/helgrind/Makefile rename to exercises/helgrind/Makefile diff --git a/code/helgrind/README.md b/exercises/helgrind/README.md similarity index 100% rename from code/helgrind/README.md rename to exercises/helgrind/README.md diff --git a/code/helgrind/fiboMT.cpp b/exercises/helgrind/fiboMT.cpp similarity index 100% rename from code/helgrind/fiboMT.cpp rename to exercises/helgrind/fiboMT.cpp diff --git a/code/helgrind/solution/fiboMT.sol.cpp b/exercises/helgrind/solution/fiboMT.sol.cpp similarity index 100% rename from code/helgrind/solution/fiboMT.sol.cpp rename to exercises/helgrind/solution/fiboMT.sol.cpp diff --git a/exercises/hello/.gitignore b/exercises/hello/.gitignore new file mode 100644 index 00000000..ce013625 --- /dev/null +++ b/exercises/hello/.gitignore @@ -0,0 +1 @@ +hello diff --git a/code/hello/CMakeLists.txt b/exercises/hello/CMakeLists.txt similarity index 100% rename from code/hello/CMakeLists.txt rename to exercises/hello/CMakeLists.txt diff --git a/code/hello/Makefile b/exercises/hello/Makefile similarity index 100% rename from code/hello/Makefile rename to exercises/hello/Makefile diff --git a/code/hello/README.md b/exercises/hello/README.md similarity index 100% rename from code/hello/README.md rename to exercises/hello/README.md diff --git a/code/hello/hello.cpp b/exercises/hello/hello.cpp similarity index 100% rename from code/hello/hello.cpp rename to exercises/hello/hello.cpp diff --git a/code/hello/hello.hpp b/exercises/hello/hello.hpp similarity index 100% rename from code/hello/hello.hpp rename to exercises/hello/hello.hpp diff --git a/code/hello/main.cpp b/exercises/hello/main.cpp similarity index 100% rename from code/hello/main.cpp rename to exercises/hello/main.cpp diff --git a/exercises/loopsRefsAuto/.gitignore b/exercises/loopsRefsAuto/.gitignore new file mode 100644 index 00000000..e757cd3a --- /dev/null +++ b/exercises/loopsRefsAuto/.gitignore @@ -0,0 +1,2 @@ +loopsRefsAuto +loopsRefsAuto.sol diff --git a/code/loopsRefsAuto/CMakeLists.txt b/exercises/loopsRefsAuto/CMakeLists.txt similarity index 100% rename from code/loopsRefsAuto/CMakeLists.txt rename to exercises/loopsRefsAuto/CMakeLists.txt diff --git a/code/loopsRefsAuto/Makefile b/exercises/loopsRefsAuto/Makefile similarity index 100% rename from code/loopsRefsAuto/Makefile rename to exercises/loopsRefsAuto/Makefile diff --git a/code/loopsRefsAuto/README.md b/exercises/loopsRefsAuto/README.md similarity index 100% rename from code/loopsRefsAuto/README.md rename to exercises/loopsRefsAuto/README.md diff --git a/code/loopsRefsAuto/loopsRefsAuto.cpp b/exercises/loopsRefsAuto/loopsRefsAuto.cpp similarity index 90% rename from code/loopsRefsAuto/loopsRefsAuto.cpp rename to exercises/loopsRefsAuto/loopsRefsAuto.cpp index bc0e5ced..0cb63fa4 100644 --- a/code/loopsRefsAuto/loopsRefsAuto.cpp +++ b/exercises/loopsRefsAuto/loopsRefsAuto.cpp @@ -17,7 +17,8 @@ int main() { DontCopyMe collection[10]; // Task 1: - // Write a for loop that initialises each struct's resultA and resultB with ascending integers. + // Write a for loop that initialises resultA and resultB for each element in the above array + // with sensible numbers. // Verify the output of the program before and after you do this. diff --git a/code/loopsRefsAuto/solution/loopsRefsAuto.sol.cpp b/exercises/loopsRefsAuto/solution/loopsRefsAuto.sol.cpp similarity index 90% rename from code/loopsRefsAuto/solution/loopsRefsAuto.sol.cpp rename to exercises/loopsRefsAuto/solution/loopsRefsAuto.sol.cpp index df04f05c..b248f843 100644 --- a/code/loopsRefsAuto/solution/loopsRefsAuto.sol.cpp +++ b/exercises/loopsRefsAuto/solution/loopsRefsAuto.sol.cpp @@ -17,7 +17,8 @@ int main() { DontCopyMe collection[10]; // Task 1: - // Write a for loop that initialises each struct's resultA and resultB with ascending integers. + // Write a for loop that initialises resultA and resultB for each element in the above array + // with sensible numbers. // Verify the output of the program before and after you do this. for ( int i = 0 ; i<10 ; ++i ) { diff --git a/exercises/memcheck/.gitignore b/exercises/memcheck/.gitignore new file mode 100644 index 00000000..b543e843 --- /dev/null +++ b/exercises/memcheck/.gitignore @@ -0,0 +1,2 @@ +memleak +memleak.sol diff --git a/code/memcheck/CMakeLists.txt b/exercises/memcheck/CMakeLists.txt similarity index 100% rename from code/memcheck/CMakeLists.txt rename to exercises/memcheck/CMakeLists.txt diff --git a/code/memcheck/Makefile b/exercises/memcheck/Makefile similarity index 100% rename from code/memcheck/Makefile rename to exercises/memcheck/Makefile diff --git a/code/memcheck/Polygons.cpp b/exercises/memcheck/Polygons.cpp similarity index 100% rename from code/memcheck/Polygons.cpp rename to exercises/memcheck/Polygons.cpp diff --git a/code/memcheck/Polygons.hpp b/exercises/memcheck/Polygons.hpp similarity index 100% rename from code/memcheck/Polygons.hpp rename to exercises/memcheck/Polygons.hpp diff --git a/code/memcheck/README.md b/exercises/memcheck/README.md similarity index 100% rename from code/memcheck/README.md rename to exercises/memcheck/README.md diff --git a/code/memcheck/memleak.cpp b/exercises/memcheck/memleak.cpp similarity index 100% rename from code/memcheck/memleak.cpp rename to exercises/memcheck/memleak.cpp diff --git a/code/memcheck/solution/Polygons.sol.cpp b/exercises/memcheck/solution/Polygons.sol.cpp similarity index 100% rename from code/memcheck/solution/Polygons.sol.cpp rename to exercises/memcheck/solution/Polygons.sol.cpp diff --git a/code/memcheck/solution/Polygons.sol.hpp b/exercises/memcheck/solution/Polygons.sol.hpp similarity index 100% rename from code/memcheck/solution/Polygons.sol.hpp rename to exercises/memcheck/solution/Polygons.sol.hpp diff --git a/code/memcheck/solution/memleak.sol.cpp b/exercises/memcheck/solution/memleak.sol.cpp similarity index 100% rename from code/memcheck/solution/memleak.sol.cpp rename to exercises/memcheck/solution/memleak.sol.cpp diff --git a/exercises/modern_oo/.gitignore b/exercises/modern_oo/.gitignore new file mode 100644 index 00000000..51fcd463 --- /dev/null +++ b/exercises/modern_oo/.gitignore @@ -0,0 +1,2 @@ +particles +particles.sol diff --git a/code/modern_oo/CMakeLists.txt b/exercises/modern_oo/CMakeLists.txt similarity index 100% rename from code/modern_oo/CMakeLists.txt rename to exercises/modern_oo/CMakeLists.txt diff --git a/code/modern_oo/Makefile b/exercises/modern_oo/Makefile similarity index 100% rename from code/modern_oo/Makefile rename to exercises/modern_oo/Makefile diff --git a/code/modern_oo/README.md b/exercises/modern_oo/README.md similarity index 100% rename from code/modern_oo/README.md rename to exercises/modern_oo/README.md diff --git a/code/modern_oo/particles.cpp b/exercises/modern_oo/particles.cpp similarity index 100% rename from code/modern_oo/particles.cpp rename to exercises/modern_oo/particles.cpp diff --git a/code/modern_oo/solution/particles.sol.cpp b/exercises/modern_oo/solution/particles.sol.cpp similarity index 100% rename from code/modern_oo/solution/particles.sol.cpp rename to exercises/modern_oo/solution/particles.sol.cpp diff --git a/exercises/modules/.gitignore b/exercises/modules/.gitignore new file mode 100644 index 00000000..00d0a6d1 --- /dev/null +++ b/exercises/modules/.gitignore @@ -0,0 +1,3 @@ +modules +solution/modules +solution/gcm.cache diff --git a/code/modules/Complex.hpp b/exercises/modules/Complex.hpp similarity index 100% rename from code/modules/Complex.hpp rename to exercises/modules/Complex.hpp diff --git a/code/modules/Makefile b/exercises/modules/Makefile similarity index 100% rename from code/modules/Makefile rename to exercises/modules/Makefile diff --git a/code/modules/README.md b/exercises/modules/README.md similarity index 100% rename from code/modules/README.md rename to exercises/modules/README.md diff --git a/code/modules/main.cpp b/exercises/modules/main.cpp similarity index 100% rename from code/modules/main.cpp rename to exercises/modules/main.cpp diff --git a/code/modules/solution/Complex.cpp b/exercises/modules/solution/Complex.cpp similarity index 100% rename from code/modules/solution/Complex.cpp rename to exercises/modules/solution/Complex.cpp diff --git a/code/modules/solution/Makefile b/exercises/modules/solution/Makefile similarity index 100% rename from code/modules/solution/Makefile rename to exercises/modules/solution/Makefile diff --git a/code/modules/solution/main.cpp b/exercises/modules/solution/main.cpp similarity index 100% rename from code/modules/solution/main.cpp rename to exercises/modules/solution/main.cpp diff --git a/exercises/move/.gitignore b/exercises/move/.gitignore new file mode 100644 index 00000000..a1c69d40 --- /dev/null +++ b/exercises/move/.gitignore @@ -0,0 +1,2 @@ +trymove +trymove.sol diff --git a/code/move/CMakeLists.txt b/exercises/move/CMakeLists.txt similarity index 100% rename from code/move/CMakeLists.txt rename to exercises/move/CMakeLists.txt diff --git a/code/move/Makefile b/exercises/move/Makefile similarity index 100% rename from code/move/Makefile rename to exercises/move/Makefile diff --git a/code/move/README.md b/exercises/move/README.md similarity index 100% rename from code/move/README.md rename to exercises/move/README.md diff --git a/code/move/solution/trymove.sol.cpp b/exercises/move/solution/trymove.sol.cpp similarity index 100% rename from code/move/solution/trymove.sol.cpp rename to exercises/move/solution/trymove.sol.cpp diff --git a/code/move/trymove.cpp b/exercises/move/trymove.cpp similarity index 100% rename from code/move/trymove.cpp rename to exercises/move/trymove.cpp diff --git a/exercises/operators/.gitignore b/exercises/operators/.gitignore new file mode 100644 index 00000000..30d8add5 --- /dev/null +++ b/exercises/operators/.gitignore @@ -0,0 +1,2 @@ +operators +operators_sol diff --git a/code/operators/CMakeLists.txt b/exercises/operators/CMakeLists.txt similarity index 67% rename from code/operators/CMakeLists.txt rename to exercises/operators/CMakeLists.txt index 46def681..2c9f31da 100644 --- a/code/operators/CMakeLists.txt +++ b/exercises/operators/CMakeLists.txt @@ -4,10 +4,11 @@ project( operators LANGUAGES CXX ) # Set up the compilation environment. include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) +set( CMAKE_CXX_STANDARD 20 ) # Create the user's executable. add_executable( operators "operators.cpp" ) # Create the "solution executable". -add_executable( operators.sol EXCLUDE_FROM_ALL "solution/operators.sol.cpp" ) -add_dependencies( solution operators.sol ) +add_executable( operators_sol EXCLUDE_FROM_ALL "solution/operators_sol.cpp" ) +add_dependencies( solution operators_sol ) diff --git a/exercises/operators/Makefile b/exercises/operators/Makefile new file mode 100644 index 00000000..fd5f735f --- /dev/null +++ b/exercises/operators/Makefile @@ -0,0 +1,11 @@ +all: operators +solution: operators_sol + +clean: + rm -f *o operators *~ operators_sol + +operators : operators.cpp + ${CXX} -g -std=c++20 -O0 -Wall -Wextra -L. -o $@ $< + +operators_sol : solution/operators_sol.cpp + ${CXX} -g -std=c++20 -O0 -Wall -Wextra -L. -o $@ $< diff --git a/exercises/operators/README.md b/exercises/operators/README.md new file mode 100644 index 00000000..a1a644bd --- /dev/null +++ b/exercises/operators/README.md @@ -0,0 +1,26 @@ + +## Instructions + +STEP 1 +- Add a free operator<<, reusing str(), and simplify main() first lines. +- Replace equal() with operator==(), and upgrade tests. +- Add operator!=(), reusing operator==(), and upgrade tests. +- Replace compare() with operator<=>(), reusing <=> between doubles, + and upgrade tests. +- Replace multiply() with operator*(), and upgrade tests. + +STEP 2 +- Replace TestResultPrinter::process() with operator()(), and upgrade CHECK(). + +OPTIONAL STEP 3 +- Add an inplace multiplication operator*=(), and add tests. +- Review operator*() so to reuse operator*=(). +- Ensure calls to operator*=() can be chained, the same as operator<<(). + +## Take away + +- Do not confuse equality and equivalence. +- We can very often implement an arithmetic operator@ in terms of operator@=. +- When implementing <=>, you get <, >, <=, >= for free. +- Object-functions are very used with standard algorithms, + yet tend to be often replaced by lambdas in modern C++. diff --git a/exercises/operators/operators.cpp b/exercises/operators/operators.cpp new file mode 100644 index 00000000..625c9c25 --- /dev/null +++ b/exercises/operators/operators.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include + +class Fraction { + +public: + + Fraction(int a_num, int a_denom = 1) : m_num(a_num), m_denom(a_denom) {} + + std::string str() const { + std::ostringstream oss; + oss << m_num << '/' << m_denom; + return oss.str(); + } + + friend bool equal( Fraction const & lhs, Fraction const & rhs ) { + return (lhs.m_num==rhs.m_num) && (lhs.m_denom==rhs.m_denom); + } + + friend int compare( Fraction const & lhs, Fraction const & rhs ) { + int v1 = lhs.m_num * rhs.m_denom; + int v2 = rhs.m_num * lhs.m_denom; + if (v1 < v2) return -1; + else if (v1 > v2) return 1; + else return 0; + } + + friend Fraction multiply( Fraction const & lhs, Fraction const & rhs ) { + return {lhs.m_num * rhs.m_num, lhs.m_denom * rhs.m_denom}; + } + + Fraction normalized() const { + const int gcd = std::gcd(m_num, m_denom); + return {m_num/gcd, m_denom/gcd}; + } + +private: + + int m_num, m_denom; + +}; + +class TestResultPrinter { + +public: + + TestResultPrinter( unsigned int a_width ) : m_width(a_width) {} + + void process(std::string const & what, bool passed) { + std::cout << std::left << std::setw(m_width) << what << ": " << (passed ? "PASS" : "** FAIL **") << '\n'; + } + +private: + + unsigned int m_width; + +}; + +// This is using the cpp, the C preprocessor to expand a bit of code +// (the what argument) to a pair containing a string representation +// of it and the code itself. That way, print is given a string and a +// value where the string is the code that lead to the value +#define CHECK(printer,what) printer.process(#what, what) + +int main() { + + // create a fraction with values 3 (which is 3/1) and 1/3 + std::cout<0); + CHECK(p2,compare(third,Fraction{2,4})<0); + + // multiply + std::cout< +#include +#include +#include +#include + +class Fraction { + +public: + + Fraction(int a_num, int a_denom = 1) : m_num(a_num), m_denom(a_denom) {} + + std::string str() const { + std::ostringstream oss; + oss << m_num << '/' << m_denom; + return oss.str(); + } + + friend bool operator==(Fraction const & lhs, Fraction const & rhs) { + return lhs.m_num == rhs.m_num && lhs.m_denom == rhs.m_denom; + } + + friend bool operator!=(Fraction const & lhs, Fraction const & rhs) { + return !(lhs==rhs); + } + + friend auto operator<=>( Fraction const & lhs, Fraction const & rhs ) { + return ((lhs.m_num*rhs.m_denom)<=>(rhs.m_num*lhs.m_denom)); + } + + Fraction & operator*=(Fraction const & other) { + m_num *= other.m_num; + m_denom *= other.m_denom; + return *this; + } + + friend Fraction operator*( Fraction lhs, Fraction const & rhs ) { + return lhs*=rhs; + } + + Fraction normalized() const { + const int gcd = std::gcd(m_num, m_denom); + return {m_num/gcd, m_denom/gcd}; + } + +private: + + int m_num, m_denom; + +}; + +std::ostream & operator<<(std::ostream & os, Fraction const & f) { + return (os<Fraction{2,6})); + CHECK(p2,std::is_gt(third<=>Fraction{1,4})); + CHECK(p2,std::is_lt(third<=>Fraction{2,4})); + CHECK(p2,(third>Fraction{1,4})); + CHECK(p2,(third=Fraction{2,4})); + CHECK(p2,(third>=Fraction{1,4})); + CHECK(p2,(third<=Fraction{2,4})); + CHECK(p2,(third>=Fraction{1,3})); + CHECK(p2,(third<=Fraction{2,3})); + CHECK(p2,!(thirdFraction{2,4})); + CHECK(p2,!(thirdFraction{2,3})); + + // multiply + std::cout<Fraction{1,1})); + CHECK(p3,std::is_eq((3*third)<=>Fraction{1,1})); + CHECK(p3,((3*third).normalized()==1)); + + // multiply in place + std::cout<1)); + CHECK(p4,one.normalized()==1); + CHECK(p4,one!=1); + + // end + std::cout<` rather than a double, and the program prints `nothing` rather than `nan` for the call with `-10`. It will also require -to modify `square`. +to update `square`. */ diff --git a/code/optional/solution/optional.sol.cpp b/exercises/optional/solution/optional.sol.cpp similarity index 100% rename from code/optional/solution/optional.sol.cpp rename to exercises/optional/solution/optional.sol.cpp diff --git a/exercises/polymorphism/.gitignore b/exercises/polymorphism/.gitignore new file mode 100644 index 00000000..6b2be516 --- /dev/null +++ b/exercises/polymorphism/.gitignore @@ -0,0 +1,2 @@ +trypoly +trypoly.sol diff --git a/code/polymorphism/CMakeLists.txt b/exercises/polymorphism/CMakeLists.txt similarity index 100% rename from code/polymorphism/CMakeLists.txt rename to exercises/polymorphism/CMakeLists.txt diff --git a/code/polymorphism/Makefile b/exercises/polymorphism/Makefile similarity index 100% rename from code/polymorphism/Makefile rename to exercises/polymorphism/Makefile diff --git a/exercises/polymorphism/Polygons.cpp b/exercises/polymorphism/Polygons.cpp new file mode 100644 index 00000000..a94383a0 --- /dev/null +++ b/exercises/polymorphism/Polygons.cpp @@ -0,0 +1,19 @@ +#include "Polygons.hpp" +#include +#include + +RegularPolygon::RegularPolygon(int n, float radius) : m_nbSides(n), m_radius(radius) {}; + +float RegularPolygon::computePerimeter() const { + std::cout << "Polygon::computePerimeter is being called\n"; + return 2 * m_nbSides * std::sin(static_cast(M_PI) / m_nbSides) * m_radius; +} + +Pentagon::Pentagon(float radius) : RegularPolygon(5, radius) {} + +Hexagon::Hexagon(float radius) : RegularPolygon(6, radius) {} + +float Hexagon::computePerimeter() const { + std::cout << "Hexagon::computePerimeter is being called\n"; + return 6 * m_radius; +} diff --git a/code/polymorphism/Polygons.hpp b/exercises/polymorphism/Polygons.hpp similarity index 65% rename from code/polymorphism/Polygons.hpp rename to exercises/polymorphism/Polygons.hpp index 9c164ba0..a7715f81 100644 --- a/code/polymorphism/Polygons.hpp +++ b/exercises/polymorphism/Polygons.hpp @@ -1,20 +1,20 @@ #pragma once -class Polygon { +class RegularPolygon { public: - Polygon(int n, float radius); + RegularPolygon(int n, float radius); float computePerimeter() const; protected: int m_nbSides; float m_radius; }; -class Pentagon : public Polygon { +class Pentagon : public RegularPolygon { public: Pentagon(float radius); }; -class Hexagon : public Polygon { +class Hexagon : public RegularPolygon { public: Hexagon(float radius); // 6*radius is easier than generic case diff --git a/code/polymorphism/README.md b/exercises/polymorphism/README.md similarity index 100% rename from code/polymorphism/README.md rename to exercises/polymorphism/README.md diff --git a/code/polymorphism/solution/trypoly.sol.cpp b/exercises/polymorphism/solution/trypoly.sol.cpp similarity index 95% rename from code/polymorphism/solution/trypoly.sol.cpp rename to exercises/polymorphism/solution/trypoly.sol.cpp index e598bee2..50325d41 100644 --- a/code/polymorphism/solution/trypoly.sol.cpp +++ b/exercises/polymorphism/solution/trypoly.sol.cpp @@ -12,7 +12,7 @@ int main() { // create a Hexagon, call the perimeter method through a reference to Polygon Hexagon hexa2{1.0}; - Polygon &poly = hexa2; + RegularPolygon &poly = hexa2; std::cout << "Hexa : perimeter = " << hexa2.computePerimeter() << '\n' << "Hexa as Poly : perimeter = " << poly.computePerimeter() << '\n'; diff --git a/code/polymorphism/trypoly.cpp b/exercises/polymorphism/trypoly.cpp similarity index 100% rename from code/polymorphism/trypoly.cpp rename to exercises/polymorphism/trypoly.cpp diff --git a/code/python/CMakeLists.txt b/exercises/python/CMakeLists.txt similarity index 100% rename from code/python/CMakeLists.txt rename to exercises/python/CMakeLists.txt diff --git a/code/python/Complex.hpp b/exercises/python/Complex.hpp similarity index 100% rename from code/python/Complex.hpp rename to exercises/python/Complex.hpp diff --git a/code/python/Makefile b/exercises/python/Makefile similarity index 100% rename from code/python/Makefile rename to exercises/python/Makefile diff --git a/code/python/README.md b/exercises/python/README.md similarity index 100% rename from code/python/README.md rename to exercises/python/README.md diff --git a/code/python/mandel.cpp b/exercises/python/mandel.cpp similarity index 100% rename from code/python/mandel.cpp rename to exercises/python/mandel.cpp diff --git a/code/python/mandel.hpp b/exercises/python/mandel.hpp similarity index 100% rename from code/python/mandel.hpp rename to exercises/python/mandel.hpp diff --git a/code/python/mandel.py b/exercises/python/mandel.py similarity index 100% rename from code/python/mandel.py rename to exercises/python/mandel.py diff --git a/code/python/mandel_cwrapper.cpp b/exercises/python/mandel_cwrapper.cpp similarity index 100% rename from code/python/mandel_cwrapper.cpp rename to exercises/python/mandel_cwrapper.cpp diff --git a/code/python/mandel_cwrapper.hpp b/exercises/python/mandel_cwrapper.hpp similarity index 100% rename from code/python/mandel_cwrapper.hpp rename to exercises/python/mandel_cwrapper.hpp diff --git a/code/python/mandel_module.cpp b/exercises/python/mandel_module.cpp similarity index 100% rename from code/python/mandel_module.cpp rename to exercises/python/mandel_module.cpp diff --git a/code/python/solution/mandel.sol.py b/exercises/python/solution/mandel.sol.py similarity index 100% rename from code/python/solution/mandel.sol.py rename to exercises/python/solution/mandel.sol.py diff --git a/code/python/solution/mandel.solctype.py b/exercises/python/solution/mandel.solctype.py similarity index 100% rename from code/python/solution/mandel.solctype.py rename to exercises/python/solution/mandel.solctype.py diff --git a/exercises/race/.gitignore b/exercises/race/.gitignore new file mode 100644 index 00000000..76613f70 --- /dev/null +++ b/exercises/race/.gitignore @@ -0,0 +1,3 @@ +racing +racing.sol1 +racing.sol2 diff --git a/exercises/race/CMakeLists.txt b/exercises/race/CMakeLists.txt new file mode 100644 index 00000000..07c789f8 --- /dev/null +++ b/exercises/race/CMakeLists.txt @@ -0,0 +1,24 @@ +# Set up the project. +cmake_minimum_required( VERSION 3.12 ) +project( race LANGUAGES CXX ) + +# Set up the compilation environment. +include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) + +# Figure out how to use the platform's thread capabilities. +find_package( Threads REQUIRED ) + +# Create the user's executable. +add_executable( racing "racing.cpp" ) +target_link_libraries( racing PRIVATE Threads::Threads ) + +# Create the "solution executable". +add_executable( racing.sol1 EXCLUDE_FROM_ALL "solution/racing.sol1.cpp" ) +target_link_libraries( racing.sol1 PRIVATE Threads::Threads ) +add_custom_target( solution1 ) +add_dependencies( solution1 racing.sol1 ) +add_executable( racing.sol2 EXCLUDE_FROM_ALL "solution/racing.sol2.cpp" ) +target_link_libraries( racing.sol2 PRIVATE Threads::Threads ) +add_custom_target( solution2 ) +add_dependencies( solution2 racing.sol2 ) +add_dependencies( solution solution1 solution2 ) diff --git a/exercises/race/Makefile b/exercises/race/Makefile new file mode 100644 index 00000000..129c7bd3 --- /dev/null +++ b/exercises/race/Makefile @@ -0,0 +1,17 @@ +PROGRAM_NAME=racing + +all: $(PROGRAM_NAME) +solution: $(PROGRAM_NAME).sol1 $(PROGRAM_NAME).sol2 + + +clean: + rm -f *o $(PROGRAM_NAME) *~ core $(PROGRAM_NAME).sol? + +$(PROGRAM_NAME) : $(PROGRAM_NAME).cpp + ${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $< + +$(PROGRAM_NAME).sol1 : solution/$(PROGRAM_NAME).sol1.cpp + ${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $< + +$(PROGRAM_NAME).sol2 : solution/$(PROGRAM_NAME).sol2.cpp + ${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $< diff --git a/exercises/race/README.md b/exercises/race/README.md new file mode 100644 index 00000000..3dc089a0 --- /dev/null +++ b/exercises/race/README.md @@ -0,0 +1,16 @@ + +## Instructions + +The program `racing.cpp` is incrementing a shared integer many times, within several threads, which should lead to race conditions if no specific protection is used. The values of the global parameters `nThread`, `nInc` and `nRepeat` can be custommized for your own computer. + +Tasks +- Compile and run the executable, check it races. +- If you have a bash shell, try `./run ./racing`, which keeps invoking the executable until a race condition is detected. +- (Optional) You can use `valgrind --tool=helgrind ./racing` to prove your assumption +- (Optional) If your operating system supports it, recompile with thread sanitizer. + With Makefile, use e.g. `make CXXFLAGS="-fsanitize=thread"` +- Use a `std::mutex` to fix the issue. +- See the difference in execution time, for example with `time ./racing`. + You might have to increase `nRepeat` if it completes too fast, or lower it if it takes too long. +- (Optional) Check again with `valgrind` or thread sanitizer if the problem is fixed. +- Try to use `std::atomic` instead of the mutex, and compare the execution time. diff --git a/code/race/racing.cpp b/exercises/race/racing.cpp similarity index 51% rename from code/race/racing.cpp rename to exercises/race/racing.cpp index 186131fe..5d8017dc 100644 --- a/code/race/racing.cpp +++ b/exercises/race/racing.cpp @@ -1,37 +1,40 @@ + #include #include #include /* - * This program tries to increment an integer 100 times from multiple threads. - * If the result comes out at 100*nThread, it stays silent, but it will print + * This program tries to increment an integer `nInc` times in `nThread` threads. + * If the result comes out at `nInc*nThread`, it stays silent, but it will print * an error if a race condition is detected. * If you don't see it racing, try ./run ./racing, which keeps invoking the * executable until a race condition is detected. */ -constexpr unsigned int nThread = 2; +constexpr std::size_t nThread = 10; +constexpr std::size_t nInc = 1000; +constexpr std::size_t nRepeat = 1000; int main() { int nError = 0; - for (int j = 0; j < 1000; j++) { + for (std::size_t j = 0; j < nRepeat; j++) { int a = 0; // Increment the variable a 100 times: - auto inc100 = [&a](){ - for (int i = 0; i < 100; ++i) { + auto increment = [&a](){ + for (std::size_t i = 0; i < nInc; ++i) { a++; } }; - // Start up all threads: + // Start up all threads std::vector threads; - for (unsigned int i = 0; i < nThread; ++i) threads.emplace_back(inc100); + for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment); for (auto & thread : threads) thread.join(); // Check - if (a != nThread * 100) { + if (a != nThread * nInc) { std::cerr << "Race detected! Result: " << a << '\n'; nError++; } diff --git a/code/race/run b/exercises/race/run similarity index 100% rename from code/race/run rename to exercises/race/run diff --git a/exercises/race/solution/racing.sol1.cpp b/exercises/race/solution/racing.sol1.cpp new file mode 100644 index 00000000..1d883406 --- /dev/null +++ b/exercises/race/solution/racing.sol1.cpp @@ -0,0 +1,39 @@ + +#include +#include +#include +#include + +constexpr std::size_t nThread = 10; +constexpr std::size_t nInc = 1000; +constexpr std::size_t nRepeat = 1000; + +int main() { + int nError = 0; + + for (std::size_t j = 0; j < nRepeat; j++) { + int a = 0; + std::mutex aMutex; + + // Increment the variable a 100 times: + auto increment = [&a,&aMutex](){ + for (std::size_t i = 0; i < nInc; ++i) { + std::scoped_lock lock{aMutex}; + a++; + } + }; + + // Start up all threads: + std::vector threads; + for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment); + for (auto & thread : threads) thread.join(); + + // Check + if (a != nThread * nInc) { + std::cerr << "Race detected! Result: " << a << '\n'; + nError++; + } + } + + return nError; +} diff --git a/exercises/race/solution/racing.sol2.cpp b/exercises/race/solution/racing.sol2.cpp new file mode 100644 index 00000000..2e3f2a98 --- /dev/null +++ b/exercises/race/solution/racing.sol2.cpp @@ -0,0 +1,37 @@ + +#include +#include +#include +#include + +constexpr std::size_t nThread = 10; +constexpr std::size_t nInc = 1000; +constexpr std::size_t nRepeat = 1000; + +int main() { + int nError = 0; + + for (std::size_t j = 0; j < nRepeat; j++) { + std::atomic a{0}; + + // Increment the variable a 100 times: + auto increment = [&a](){ + for (std::size_t i = 0; i < nInc; ++i) { + a++; + } + }; + + // Start up all threads + std::vector threads; + for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment); + for (auto & thread : threads) thread.join(); + + // Check + if (a != nThread * nInc) { + std::cerr << "Race detected! Result: " << a << '\n'; + nError++; + } + } + + return nError; +} diff --git a/exercises/smartPointers/.gitignore b/exercises/smartPointers/.gitignore new file mode 100644 index 00000000..f6bbf861 --- /dev/null +++ b/exercises/smartPointers/.gitignore @@ -0,0 +1,10 @@ +problem1 +problem2 +problem3 +problem4 +problem5 +problem1.sol +problem2.sol +problem3.sol +problem4.sol +problem5.sol diff --git a/exercises/smartPointers/CMakeLists.txt b/exercises/smartPointers/CMakeLists.txt new file mode 100644 index 00000000..1d13482c --- /dev/null +++ b/exercises/smartPointers/CMakeLists.txt @@ -0,0 +1,22 @@ +# Set up the project. +cmake_minimum_required( VERSION 3.12 ) +project( smartPointers LANGUAGES CXX ) + +# Set up the compilation environment. +include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) +set( CMAKE_CXX_STANDARD 20 ) + +# Create the user's executable. +add_executable( problem1 "problem1.cpp" ) +add_executable( problem2 "problem2.cpp" ) +add_executable( problem3 "problem3.cpp" ) +add_executable( problem4 "problem4.cpp" ) +add_executable( problem5 "problem5.cpp" ) + +# Create the "solution executables". +add_executable( problem1.sol EXCLUDE_FROM_ALL "solution/problem1.sol.cpp" ) +add_executable( problem2.sol EXCLUDE_FROM_ALL "solution/problem2.sol.cpp" ) +add_executable( problem3.sol EXCLUDE_FROM_ALL "solution/problem3.sol.cpp" ) +add_executable( problem4.sol EXCLUDE_FROM_ALL "solution/problem4.sol.cpp" ) +add_executable( problem5.sol EXCLUDE_FROM_ALL "solution/problem5.sol.cpp" ) +add_dependencies( solution problem1.sol problem2.sol problem3.sol problem4.sol problem5.sol ) diff --git a/exercises/smartPointers/Makefile b/exercises/smartPointers/Makefile new file mode 100644 index 00000000..59041117 --- /dev/null +++ b/exercises/smartPointers/Makefile @@ -0,0 +1,11 @@ +all: problem1 problem2 problem3 problem4 problem5 +solution: problem1.sol problem2.sol problem3.sol problem4.sol problem5.sol + +clean: + rm -f *o *so *~ problem? problem?.sol + +% : %.cpp + $(CXX) -g -std=c++20 -Wall -Wextra -o $@ $< + +%.sol : solution/%.sol.cpp + $(CXX) -g -std=c++20 -Wall -Wextra -o $@ $< diff --git a/exercises/smartPointers/README.md b/exercises/smartPointers/README.md new file mode 100644 index 00000000..5c710bb9 --- /dev/null +++ b/exercises/smartPointers/README.md @@ -0,0 +1,24 @@ + +# Writing leak-free and fault-free C++ + +Here we have five code snippets that will benefit from using smart pointers. By replacing every explicit `new` with `make_unique` or `make_shared`, (alternatively by explicitly instantiating smart pointers) we will fix memory leaks, segmentation faults, and make most cleanup code unnecessary. + +## Prerequisites + +* Do you know which kind of pointer is used for what? + * Raw pointer + * [`std::unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr) + * [`std::shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr) +* C++-14 for `std::make_unique` / `std::make_shared`. Understand what these functions do. +* Helpful: Move semantics for `problem2()`, but one can do without. + +## Instructions + +* In the **essentials course**, work on `problem1` and `problem2`, and fix the leaks using smart pointers. +* In the **advanced course**, work on `problem1` to `problem5`. Skip `problem4` and `problem5` if you don't have enough time. +* Dedicated instructions are given in each cpp file. +* Each one is written so that you easily check if the problem is solved or not. +* If seen in course before, you are also advised to try external tools such as valgrind: +``` +valgrind --leak-check=full --track-origins=yes ./problem1 +``` diff --git a/exercises/smartPointers/problem1.cpp b/exercises/smartPointers/problem1.cpp new file mode 100644 index 00000000..1741aca7 --- /dev/null +++ b/exercises/smartPointers/problem1.cpp @@ -0,0 +1,72 @@ + + +#include +#include + + +/* -------------------------------------------------------------------------------------------- + * Unique ownership. + * + * Always use smart pointers instead of `new`. A frequent source of memory leaks is a function + * that terminates in an unexpected way. + * + * Tasks + * 1) Compile and run the code below. Notice that the final count is `1`, + * showing that the instance of LargeObject has not been deallocated. + * 2) Modify `doStuff()` (only) so to use a `std::unique_ptr` instead of a raw pointer. + * The final count should be `0`, and the memory leak solved. + * -------------------------------------------------------------------------------------------- + */ + + +// The class LargeObject emulates a large object. +// One should avoid to copy it, and rather use +// a pointer to pass it around. + +struct LargeObject { + + std::array data ; + + // So to check for some potential memory leak, + // we count the constructions and destructions + inline static std::size_t count = 0; + LargeObject() { count++ ; } + ~LargeObject() { count-- ; } + +} ; + +// A function to do something with a large object. +// Here we simulate that an error happens. + +void changeLargeObject( LargeObject & object ) { + + object.data[0] = 1. ; + throw std::invalid_argument("Error when changing object data.") ; + +} + +// Often, data are owned by one entity, and merely used by others. +// In this case, we hand the data to changeLargeObject(), +// and unfortunately, something goes wrong... + +void doStuff() { + + // MAKE YOUR CHANGES IN THIS FUNCTION + + auto obj = new LargeObject ; + changeLargeObject(*obj) ; + delete obj ; + +} + +int main() { + + try { + doStuff() ; + } catch ( const std::exception & e ) { + std::cerr<< "Terminated with exception: " << e.what() << "\n" ; + } + + std::cout<<"Leaked large objects: "< +#include +#include + + +/* -------------------------------------------------------------------------------------------- + * Collections of smart pointers. + * + * Often, one has to store pointers to objects in collections. + * Fix the memory leaks below by using `std::unique_ptr`. + * + * Tasks + * 1) Compile and run the code below. Notice that the final count is `10`, + * which is expected because the new objects are never deallocated. + * 2) Factory functions should better return smart pointers, + * because it clarifies who owns an object. + * Change the return type of the function `newLargeObject()` for a `std::unique_ptr()`. + * The vector should own the objects, so try to store them using smart pointers. + * Since the change function doesn't accept smart pointers, find a solution to pass the objects. + * Try to use `std::unique_ptr`, not `std::shared_ptr` ! + * -------------------------------------------------------------------------------------------- + */ + + +// The class LargeObject emulates a large object. +// One should avoid to copy it, and rather use +// a pointer to pass it around. + +struct LargeObject { + + std::array data ; + + // So to check for some potential memory leak, + // we count the constructions and destructions + inline static std::size_t count = 0; + LargeObject() { count++ ; } + ~LargeObject() { count-- ; } + +} ; + +// A factory function to create large objects. + +LargeObject * newLargeObject() { + + // MAKE YOUR CHANGES IN THIS FUNCTION + + auto object = new LargeObject() ; + // Imagine there is more setup steps of "object" here + // ... + return object ; + +} + +// A function to do something with the objects. +// Note that since we don't own the object, +// we don't need a smart pointer as argument. + +void changeLargeObject( LargeObject & object ) { + + object.data[0] = 1. ; + +} + +void doStuff() { + + // MAKE YOUR CHANGES IN THIS FUNCTION + + std::vector largeObjects ; + + for ( unsigned int i = 0 ; i < 10 ; ++i ) { + auto newObj = newLargeObject() ; + // ... additional newObj setup ... + largeObjects.push_back(newObj) ; + } + + for ( const auto & obj : largeObjects ) { + changeLargeObject(*obj) ; + } +} + +int main() { + + doStuff() ; + std::cout<<"Leaked large objects: "< +#include +#include +#include +#include + + +/* -------------------------------------------------------------------------------------------- + * Shared ownership. + * + * Most of the time, ownership can be solved by having one owner (with `std::unique_ptr`) and + * one or more observers (raw pointers or references). Sometimes, we need to truly share data, + * though. Here is an example of a completely messed up ownership model, which could be + * fixed using shared_ptr. + * + * Tasks + * 1) Verify the mess by repeatedly running it using such a command like: + * `while true; do ./problem3 ; done` + * You should notice that the program regularly leaks. + * 2) Fix the ownership model using `std::shared_ptr` ! + * - Convert the vectors to holding `std::shared_ptr`. + * - Fix the arguments of the functions. + * 3) Speed optimisation: make sure that you don't create & destroy useless `std::shared_ptr`, + * which is slow, for example in the for loop of `doStuff()` and when + * calling `changeLargeObject()`. + * -------------------------------------------------------------------------------------------- + */ + + +// The class LargeObject emulates a large object. +// One should avoid to copy it, and rather use +// a pointer to pass it around. + +struct LargeObject { + + std::array data ; + + // So to check for some potential memory leak, + // we count the constructions and destructions + inline static std::size_t count = 0; + LargeObject() { count++ ; } + ~LargeObject() { count-- ; } + +} ; + +// This removes an element from a non-owning vector, +// in a random place. Such elements can by known in +// several vectors, so they must not be deleted. + +void removeRandom( std::vector & collection, std::default_random_engine & engine ) { + + // MAKE YOUR CHANGES IN THIS FUNCTION + + auto pos = collection.begin() + engine() % collection.size() ; + collection.erase(pos); + +} + +// A function to do something with a large object. +// Note that since we don't own the object, +// we don't need a smart pointer as argument. + +void changeLargeObject( LargeObject & object ) { + + object.data[0] = 1. ; + +} + +// Global stuff: we have pointers to objects duplicated in two different collections. +// We work a bit with the collections, and then we try to clean up without neither +// memory leak nor segmentation fault. Without a shared ownership model, this becomes a mess. + +void doStuff() { + + // MAKE YOUR CHANGES IN THIS FUNCTION + + // Prepare a non deterministic random engine + + std::random_device device ; + std::default_random_engine engine(device()) ; + + // Original collection + + std::vector objVector(10); + for ( auto & ptr : objVector ) { + ptr = new LargeObject(); + } + + // Let's copy the whole collection + + auto objVectorCopy(objVector); + + // Random work with the objects + + removeRandom(objVector,engine); + removeRandom(objVectorCopy,engine); + removeRandom(objVectorCopy,engine); + // ... + for (auto objPtr : objVector ) { + changeLargeObject(*objPtr) ; + } + + // ONCE YOU FIXED CODE ABOVE WITH SHARED POINTERS + // THE UGLY CODE BELOW SHOULD BECOME UNNECESSARY + + for ( auto objPtr : objVector ) { + delete objPtr ; + } + for ( auto objPtr : objVectorCopy ) { + // If the element is in the original collection, it was already deleted. + if (std::find(objVector.begin(), objVector.end(), objPtr) == objVector.end()) { + delete objPtr; + } + } + +} + +int main() { + + doStuff() ; + std::cout<<"Leaked large objects: "< +#include +#include + + +/* -------------------------------------------------------------------------------------------- + * Smart pointers as class members. + * + * Class members that are pointers can quickly become a problem. + * Firstly, if only raw pointers are used, the intended ownership is unclear. + * Secondly, it's easy to overlook that a member has to be deleted. + * Thirdly, pointer members usually require you to implement copy or move constructors + * and assignment operators (--> rule of 3, rule of 5). + * Since C++-11, one can solve a few of those problems using smart pointers. + * + * The class "Owner" owns some data, but it is broken. If you copy it like in + * doStuff(), you have two pointers pointing to the same data, but both instances + * think that they own the data. + * + * Tasks + * 1) It likely crashes. Verify this. You can also try running `valgrind ./problem4`, + * it should give you some hints as to what's happening. + * 2) Fix the Owner class by using a `std::shared_ptr` for its data, which we can + * copy as much as we want. Run the fixed program. + * Note: Once `std::shared_ptr` is in use, you can also use the default destructor. + * + * -------------------------------------------------------------------------------------------- + */ + +struct LargeObject { + + std::array data ; + +} ; + +class Owner { + + // MAKE YOUR CHANGES IN THIS CLASS + + public: + + Owner() : _largeObject( new LargeObject() ) {} + LargeObject * getLargeObject() { return _largeObject ; } + ~Owner() { delete _largeObject ; } + + private: + + LargeObject * _largeObject ; + +} ; + +void doStuff() { + + std::vector owners ; + + for ( int i = 0 ; i < 5 ; ++i ) { + Owner owner ; + // ... additional owner setup ... + owners.push_back(owner) ; + } + + /* Now we have a problem: + * We created Owner instances on the stack, and copied them into the vector. + * When the instances on the stack are destroyed, the memory is deallocated. + * All copies in the vector now point to the deallocated memory! + * We could fix this using copy constructors (but we don't want to copy the data), + * using move semantics or using shared_ptr. + * Here, we want to go for shared_ptr. + */ + +} + +int main() { + + doStuff() ; + +} diff --git a/exercises/smartPointers/problem5.cpp b/exercises/smartPointers/problem5.cpp new file mode 100644 index 00000000..8a6f9ea4 --- /dev/null +++ b/exercises/smartPointers/problem5.cpp @@ -0,0 +1,113 @@ + + +#include +#include +#include +#include + + +/* -------------------------------------------------------------------------------------------- + * Weak pointers. + * + * Let's construct some `std::weak_ptr` so to observe some `std::shared_ptr`. + * This weak pointers can be used to retreive the object pointed by the corresponding `std::shared_ptr`, + * but it does not increase the reference count of the objects, and does not prevent + * the deletion of the underlying objects if all shared pointers go out of scope. + * To *use* the observed data, one has to create a `std::shared_ptr` from the `std::weak_ptr`, + * so that it is guaranteed that the underlying object is alive. + * + * In our case, the `Observer` class wants to act on the data of the `Owner`, + * but it doesn't need to own it. To do this, we use a weak pointer. + * + * Tasks + * 1) Investigate the crash. Optionally use a debugger, run in valgrind, + * compile with -fsanitize=address ... + * 2) Rewrite the interface of Owner::getData() such that the observer can see the + * `std::shared_ptr` pointing to the large object. + * Review the class `Observer` such that it stores a `std::weak pointer`. + * In `setValue`and `getValue`, access the weak pointer, and use the data *only* if the memory is still alive. + * Note: What you need is weak_ptr::lock(). Check out the documentation and the example at the bottom: + * https://en.cppreference.com/w/cpp/memory/weak_ptr/lock + * + * -------------------------------------------------------------------------------------------- + */ + +struct LargeObject { + + std::array data ; + +} ; + +class Owner { + + // SOME CHANGES NEEDED IN THIS CLASS + + public: + + Owner() : _largeObject( new LargeObject() ) {} + LargeObject * getLargeObject() const { return _largeObject.get() ; } + + private: + + std::shared_ptr _largeObject ; + +} ; + +class Observer { + + // SOME CHANGES NEEDED IN THIS CLASS + + public: + + Observer( const Owner & owner ) : _largeObject(owner.getLargeObject()) {} + + void setValue( double v ) { + if (_largeObject) { _largeObject->data[0] = v ; } + else { _largeObject->data[0] = 0. ; } + } + + double getValue() const { + if (_largeObject) { return _largeObject->data[0] ; } + else { return -1. ; } + } + + private: + + LargeObject * _largeObject ; + +} ; + +void doStuff() { + + // Owners and observers + + std::vector owners(5) ; + std::vector observers ; + for ( auto & owner : owners ) { + observers.emplace_back(owner) ; + } + + // Write through observers + + for ( auto & observer : observers ) { + observer.setValue(1.) ; + } + + // Let's destroy the 2 last owners + + owners.resize(3) ; + + // Read through observers + + std::cout << "Values:"; + for ( auto const & observer : observers ) { + std::cout<<" "< +#include +#include + +struct LargeObject { + + std::array data ; + + // So to check for some potential memory leak, + // we count the constructions and destructions + inline static std::size_t count = 0 ; + LargeObject() { count++ ; } + ~LargeObject() { count-- ; } + +} ; + +void changeLargeObject( LargeObject & object ) { + + object.data[0] = 1. ; + throw std::invalid_argument("Error when changing object data.") ; + +} + +void doStuff() { + + auto obj = std::make_unique() ; + changeLargeObject(*obj) ; + +} + +int main() { + + try { + doStuff() ; + } catch ( const std::exception & e ) { + std::cerr<< "Terminated with exception: " << e.what() << "\n" ; + } + + std::cout<<"Leaked large objects: "< +#include +#include +#include + + +struct LargeObject { + + std::array data ; + + // So to check for some potential memory leak, + // we count the constructions and destructions + inline static std::size_t count = 0 ; + LargeObject() { count++ ; } + ~LargeObject() { count-- ; } + +} ; + +std::unique_ptr newLargeObject() { + + auto object = std::make_unique() ; + // Imagine there is more setup steps of "object" here + // ... + return object ; +} + +void changeLargeObject( LargeObject & object ) { + + object.data[0] = 1. ; + +} + +void doStuff() { + + std::vector> largeObjects ; + + for ( unsigned int i = 0 ; i < 10 ; ++i ) { + largeObjects.push_back(newLargeObject()); + // ... additional largeObjects.back() setup ... + + // Alternatively, when the object is ready, + // one can "give up" newObj + // by moving it into the vector. + // auto newObj = createLargeObject() ; + // ... additional newObj setup ... + // largeObjects.push_back(std::move(newObj)); + + } + + for (const auto& obj : largeObjects) { + changeLargeObject(*obj); + } +} + +int main() { + doStuff() ; + std::cout<<"Leaked large objects: "< +#include +#include +#include +#include +#include + +struct LargeObject { + + std::array data ; + + // So to check for some potential memory leak, + // we count the constructions and destructions + inline static std::size_t count = 0 ; + LargeObject() { count++ ; } + ~LargeObject() { count-- ; } + +} ; + +void removeRandom(std::vector>& collection, std::default_random_engine & engine) { + + auto pos = collection.begin() + engine() % collection.size(); + collection.erase(pos); + +} + +void changeLargeObject( LargeObject & object ) { + + object.data[0] = 1. ; + +} + +void doStuff() { + + // Prepare a non deterministic random engine + + std::random_device device ; + std::default_random_engine engine(device()) ; + + // Original collection + + std::vector> objVector(10); + for ( auto & ptr : objVector ) { + ptr = std::make_shared(); + } + + // Less copies : + // std::vector> objVector ; + // objVector.reserve(10); + // for ( unsigned int i = 0 ; i < 10 ; ++i ) { + // objVector.emplace_back(new LargeObject()) ; + // } + + // Let's copy the whole collection + + auto objVectorCopy(objVector); + + // Random work with the objects + + removeRandom(objVector,engine); + removeRandom(objVectorCopy,engine); + removeRandom(objVectorCopy,engine); + // ... + // ... + for ( auto const & objPtr : objVector ) { + changeLargeObject(*objPtr) ; + } + +} + +int main() { + + doStuff() ; + std::cout<<"Leaked large objects: "< +#include +#include +#include + + +struct LargeObject { + + std::array data ; + +} ; + +class Owner { + + public: + + Owner() : _largeObject( new LargeObject() ) {} + LargeObject * getLargeObject() const { return _largeObject.get() ; } + + private: + + std::shared_ptr _largeObject ; + +} ; + +void doStuff() { + + std::vector owners ; + + for ( int i = 0 ; i < 5 ; ++i ) { + Owner owner ; + // ... additional owner setup ... + owners.push_back(owner) ; + } +} + +int main() { + + doStuff() ; + +} diff --git a/exercises/smartPointers/solution/problem5.sol.cpp b/exercises/smartPointers/solution/problem5.sol.cpp new file mode 100644 index 00000000..154af2fc --- /dev/null +++ b/exercises/smartPointers/solution/problem5.sol.cpp @@ -0,0 +1,83 @@ + +#include +#include +#include +#include + +struct LargeObject { + + std::array data ; + +} ; + +class Owner { + + public: + + Owner() : _largeObject( new LargeObject() ) {} + auto getLargeObject() const { return _largeObject ; } + + private: + + std::shared_ptr _largeObject ; + +} ; + +class Observer { + + public: + + Observer( const Owner & owner ) : _largeObject(owner.getLargeObject()) {} + + void setValue( double v ) { + std::shared_ptr wptr = _largeObject.lock(); + if (wptr) { wptr->data[0] = v ; } + else { wptr->data[0] = 0. ; } + } + + double getValue() const { + std::shared_ptr wptr = _largeObject.lock(); + if (wptr) { return wptr->data[0] ; } + else { return -1. ; } + } + + private: + + std::weak_ptr _largeObject ; + +} ; + +void doStuff() { + + // Owners and observers + + std::vector owners(5) ; + std::vector observers ; + for ( auto & owner : owners ) { + observers.emplace_back(owner) ; + } + + // Write through observers + + for ( auto & observer : observers ) { + observer.setValue(1.) ; + } + + // Let's destroy the 2 last owners + + owners.resize(3) ; + + // Read through observers + + std::cout << "Values:"; + for ( auto const & observer : observers ) { + std::cout<<" "< -g -std=c++17 -o undefinedBehaviour undefinedBehaviour.cpp` + +- You might see warnings depending on the compiler, but on most platforms the program runs without observable issues. + +- Recompile with "-fsanitize=undefined", and observe that almost every second line contains a serious bug. + NOTE: If this fails, your compiler does not support UBSan (yet). In this case, you can try to read the + program and catch the bugs, or continue with the next exercise. + +- Try to understand what's wrong. The solution contains a few comments what kind of bugs are hidden in the program. diff --git a/exercises/ubsan/solution/undefinedBehaviour.cpp b/exercises/ubsan/solution/undefinedBehaviour.cpp new file mode 100644 index 00000000..1101161d --- /dev/null +++ b/exercises/ubsan/solution/undefinedBehaviour.cpp @@ -0,0 +1,60 @@ +#include +#include + +// You don't need to change any code in these structs: +struct Base { + virtual void print() = 0; + virtual ~Base() {} +}; +struct Derived1 : public Base { + void print() override { + std::cout << "Derived1::print()\n"; + } +}; +struct Derived2 : public Base { + void print() override { + std::cout << "Derived2::print()\n"; + } +}; + + +/** + * *************** + * Instructions: + * *************** + * + * Compile and run this program using a compiler invocation like + * -g -std=c++17 -o undefinedBehaviour undefinedBehaviour.cpp + * + * You might see warnings depending on the compiler, but on most platforms the program runs without observable issues. + * Smart compilers will warn with most of these bugs, but the compilers can easily be deceived by hiding a nullptr in + * a variable, or passing it as a function argument or similar. + * + * Since UBSan does runtime checks, it will catch these errors even if they are obscured. + * Recompile with "-fsanitize=undefined", and observe that almost every second line contains a serious bug. + * Try to understand what's wrong. + */ + +int runTests() { + int arr[] = {1, 2, 3, 4}; + std::cout << "arr[4]=" << arr[4] << "\n"; // Array overrun + + unsigned int s = 1; + std::cout << "s << 33=" << (s << 33) << "\n"; // We cannot shift a type with 32 bits by 33 bits. + + int i = std::numeric_limits::max(); + std::cout << "i + 1 =" << i + 1 << "\n"; // Adding 1 to max int overflows to min int + + Derived1 d1; + Base & base = d1; + auto & d2 = static_cast(base); // Casting an original d1 to d2 is wrong + d2.print(); // Calling a function on a wrongly-cast object is wrong + + Derived2 * d2ptr = nullptr; + Derived2 & nullref = static_cast(*d2ptr); // Cannot bind references to a wrong address +} // Forgot to return a value + +int main() { + const auto result = runTests(); + return result; +} diff --git a/exercises/ubsan/undefinedBehaviour.cpp b/exercises/ubsan/undefinedBehaviour.cpp new file mode 100644 index 00000000..d5926385 --- /dev/null +++ b/exercises/ubsan/undefinedBehaviour.cpp @@ -0,0 +1,60 @@ +#include +#include + +// You don't need to change any code in these structs: +struct Base { + virtual void print() = 0; + virtual ~Base() {} +}; +struct Derived1 : public Base { + void print() override { + std::cout << "Derived1::print()\n"; + } +}; +struct Derived2 : public Base { + void print() override { + std::cout << "Derived2::print()\n"; + } +}; + + +/** + * *************** + * Instructions: + * *************** + * + * Compile and run this program using a compiler invocation like + * -g -std=c++17 -o undefinedBehaviour undefinedBehaviour.cpp + * + * You might see warnings depending on the compiler, but on most platforms the program runs without observable issues. + * Smart compilers will warn with most of these bugs, but the compilers can easily be deceived by hiding a nullptr in + * a variable, or passing it as a function argument or similar. + * + * Since UBSan does runtime checks, it will catch these errors even if they are obscured. + * Recompile with "-fsanitize=undefined", and observe that almost every second line contains a serious bug. + * Try to understand what's wrong. + */ + +int runTests() { + int arr[] = {1, 2, 3, 4}; + std::cout << "arr[4]=" << arr[4] << "\n"; + + unsigned int s = 1; + std::cout << "s << 33=" << (s << 33) << "\n"; + + int i = std::numeric_limits::max(); + std::cout << "i + 1 =" << i + 1 << "\n"; + + Derived1 d1; + Base & base = d1; + auto & d2 = static_cast(base); + d2.print(); + + Derived2 * d2ptr = nullptr; + Derived2 & nullref = static_cast(*d2ptr); +} + +int main() { + const auto result = runTests(); + return result; +} diff --git a/exercises/valgrind/.gitignore b/exercises/valgrind/.gitignore new file mode 100644 index 00000000..18ed3421 --- /dev/null +++ b/exercises/valgrind/.gitignore @@ -0,0 +1,2 @@ +debug +debug.sol diff --git a/code/valgrind/CMakeLists.txt b/exercises/valgrind/CMakeLists.txt similarity index 57% rename from code/valgrind/CMakeLists.txt rename to exercises/valgrind/CMakeLists.txt index 20ba2fa1..80df4f13 100644 --- a/code/valgrind/CMakeLists.txt +++ b/exercises/valgrind/CMakeLists.txt @@ -6,8 +6,8 @@ project( valgrind LANGUAGES CXX ) include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) # Create the user's executable. -add_executable( valgrind_randomize "randomize.cpp" ) +add_executable( valgrind "debug.cpp" ) # Create the "solution executable". -add_executable( valgrind_randomize.sol EXCLUDE_FROM_ALL "solution/randomize.sol.cpp" ) -add_dependencies( solution valgrind_randomize.sol ) +add_executable( valgrind.sol EXCLUDE_FROM_ALL "solution/debug.sol.cpp" ) +add_dependencies( solution valgrind.sol ) diff --git a/exercises/valgrind/Makefile b/exercises/valgrind/Makefile new file mode 100644 index 00000000..077aadfa --- /dev/null +++ b/exercises/valgrind/Makefile @@ -0,0 +1,11 @@ +all: debug +solution: debug.sol + +clean: + rm -f *o debug *~ debug.sol core + +debug : debug.cpp + ${CXX} -std=c++17 -g -O0 -L. -o $@ $< + +debug.sol : solution/debug.sol.cpp + ${CXX} -std=c++17 -g -O0 -Wall -Wextra -L. -o $@ $< diff --git a/exercises/valgrind/README.md b/exercises/valgrind/README.md new file mode 100644 index 00000000..85b79570 --- /dev/null +++ b/exercises/valgrind/README.md @@ -0,0 +1,7 @@ + +## Instructions + +* compile, run, it shouldn't crash +* run with valgrind (`valgrind ./debug`) +* fix an out-of-bounds access +* check for memory leaks diff --git a/exercises/valgrind/debug.cpp b/exercises/valgrind/debug.cpp new file mode 100644 index 00000000..eb7a917a --- /dev/null +++ b/exercises/valgrind/debug.cpp @@ -0,0 +1,44 @@ +#include +#include + +void swap(int* a, int* b) +{ + int c = *a; + *a = *b; + *b = c; +} + +void reverse(int* v, unsigned int len) +{ + for (unsigned int i = 0; i < (len + 1) / 2; i++) { + const int a = i; + const int b = len - i; + + swap(v + a, v + b); + } +} + +int* createAndFillVector(unsigned int len) +{ + auto v = new int[len]; + for (unsigned int i = 0; i < len; i++) { + v[i] = i; + } + return v; +} + +int main() +{ + constexpr auto arraySize = 100; + int* v = nullptr; + // create and reverse the vector of LEN numbers + v = createAndFillVector(arraySize); + reverse(v, arraySize); + + // check if the revert worked: + const bool isReversed = std::is_sorted(v, v + arraySize, std::greater {}); + std::cout << "Vector reversed successfully: " << std::boolalpha + << isReversed << "\n"; + + return isReversed ? 0 : 1; +} diff --git a/exercises/valgrind/solution/debug.sol.cpp b/exercises/valgrind/solution/debug.sol.cpp new file mode 100644 index 00000000..5676e620 --- /dev/null +++ b/exercises/valgrind/solution/debug.sol.cpp @@ -0,0 +1,46 @@ +#include +#include + +void swap(int* a, int* b) +{ + int c = *a; + *a = *b; + *b = c; +} + +void reverse(int* v, unsigned int len) +{ + for (unsigned int i = 0; i < (len + 1) / 2; i++) { + const int a = i; + const int b = len - 1 - i; + + swap(v + a, v + b); + } +} + +int* createAndFillVector(unsigned int len) +{ + auto v = new int[len]; + for (unsigned int i = 0; i < len; i++) { + v[i] = i; + } + return v; +} + +int main() +{ + constexpr auto arraySize = 100; + int* v = nullptr; + // create and reverse the vector of LEN numbers + v = createAndFillVector(arraySize); + reverse(v, arraySize); + + // check if the revert worked: + const bool isReversed = std::is_sorted(v, v + arraySize, std::greater {}); + std::cout << "Vector reversed successfully: " << std::boolalpha + << isReversed << "\n"; + + delete[] v; + + return isReversed ? 0 : 1; +} diff --git a/exercises/variadic/.gitignore b/exercises/variadic/.gitignore new file mode 100644 index 00000000..711c49af --- /dev/null +++ b/exercises/variadic/.gitignore @@ -0,0 +1,2 @@ +variadic +variadic.sol diff --git a/code/variadic/CMakeLists.txt b/exercises/variadic/CMakeLists.txt similarity index 100% rename from code/variadic/CMakeLists.txt rename to exercises/variadic/CMakeLists.txt diff --git a/code/variadic/Makefile b/exercises/variadic/Makefile similarity index 100% rename from code/variadic/Makefile rename to exercises/variadic/Makefile diff --git a/code/variadic/README.md b/exercises/variadic/README.md similarity index 100% rename from code/variadic/README.md rename to exercises/variadic/README.md diff --git a/code/variadic/solution/variadic.sol.cpp b/exercises/variadic/solution/variadic.sol.cpp similarity index 100% rename from code/variadic/solution/variadic.sol.cpp rename to exercises/variadic/solution/variadic.sol.cpp diff --git a/code/variadic/variadic.cpp b/exercises/variadic/variadic.cpp similarity index 100% rename from code/variadic/variadic.cpp rename to exercises/variadic/variadic.cpp diff --git a/exercises/variant/.gitignore b/exercises/variant/.gitignore new file mode 100644 index 00000000..2e6bf93e --- /dev/null +++ b/exercises/variant/.gitignore @@ -0,0 +1 @@ +variant diff --git a/code/variant/CMakeLists.txt b/exercises/variant/CMakeLists.txt similarity index 100% rename from code/variant/CMakeLists.txt rename to exercises/variant/CMakeLists.txt diff --git a/code/variant/Makefile b/exercises/variant/Makefile similarity index 100% rename from code/variant/Makefile rename to exercises/variant/Makefile diff --git a/code/variant/README.md b/exercises/variant/README.md similarity index 100% rename from code/variant/README.md rename to exercises/variant/README.md diff --git a/code/variant/solution/variant.sol1.cpp b/exercises/variant/solution/variant.sol1.cpp similarity index 99% rename from code/variant/solution/variant.sol1.cpp rename to exercises/variant/solution/variant.sol1.cpp index f42c46c5..8e3f11c1 100644 --- a/code/variant/solution/variant.sol1.cpp +++ b/exercises/variant/solution/variant.sol1.cpp @@ -1,3 +1,4 @@ + #include #include #include diff --git a/code/variant/solution/variant.sol2.cpp b/exercises/variant/solution/variant.sol2.cpp similarity index 99% rename from code/variant/solution/variant.sol2.cpp rename to exercises/variant/solution/variant.sol2.cpp index 79ba203c..4c48b64d 100644 --- a/code/variant/solution/variant.sol2.cpp +++ b/exercises/variant/solution/variant.sol2.cpp @@ -1,3 +1,4 @@ + #include #include #include diff --git a/code/variant/variant.cpp b/exercises/variant/variant.cpp similarity index 81% rename from code/variant/variant.cpp rename to exercises/variant/variant.cpp index 2b626b0e..15b8502b 100644 --- a/code/variant/variant.cpp +++ b/exercises/variant/variant.cpp @@ -1,6 +1,7 @@ /* -In the code below, replace inheritance with the use of a std::variant. +In the code below, replace inheritance with the use of a `std::variant`. +There is no more need for a base class, and no more need pointers. Two solutions are provided : 1. with `std::get_if`, 2. with `std::visit`. diff --git a/exercises/virtual_inheritance/.gitignore b/exercises/virtual_inheritance/.gitignore new file mode 100644 index 00000000..a2d52d8f --- /dev/null +++ b/exercises/virtual_inheritance/.gitignore @@ -0,0 +1,2 @@ +trymultiherit +trymultiherit.sol diff --git a/code/virtual_inheritance/CMakeLists.txt b/exercises/virtual_inheritance/CMakeLists.txt similarity index 100% rename from code/virtual_inheritance/CMakeLists.txt rename to exercises/virtual_inheritance/CMakeLists.txt diff --git a/code/virtual_inheritance/Makefile b/exercises/virtual_inheritance/Makefile similarity index 100% rename from code/virtual_inheritance/Makefile rename to exercises/virtual_inheritance/Makefile diff --git a/code/virtual_inheritance/README.md b/exercises/virtual_inheritance/README.md similarity index 100% rename from code/virtual_inheritance/README.md rename to exercises/virtual_inheritance/README.md diff --git a/code/virtual_inheritance/TextBox.cpp b/exercises/virtual_inheritance/TextBox.cpp similarity index 100% rename from code/virtual_inheritance/TextBox.cpp rename to exercises/virtual_inheritance/TextBox.cpp diff --git a/code/virtual_inheritance/TextBox.hpp b/exercises/virtual_inheritance/TextBox.hpp similarity index 100% rename from code/virtual_inheritance/TextBox.hpp rename to exercises/virtual_inheritance/TextBox.hpp diff --git a/code/virtual_inheritance/solution/TextBox.cpp b/exercises/virtual_inheritance/solution/TextBox.cpp similarity index 100% rename from code/virtual_inheritance/solution/TextBox.cpp rename to exercises/virtual_inheritance/solution/TextBox.cpp diff --git a/code/virtual_inheritance/solution/TextBox.hpp b/exercises/virtual_inheritance/solution/TextBox.hpp similarity index 100% rename from code/virtual_inheritance/solution/TextBox.hpp rename to exercises/virtual_inheritance/solution/TextBox.hpp diff --git a/code/virtual_inheritance/solution/trymultiherit.sol.cpp b/exercises/virtual_inheritance/solution/trymultiherit.sol.cpp similarity index 100% rename from code/virtual_inheritance/solution/trymultiherit.sol.cpp rename to exercises/virtual_inheritance/solution/trymultiherit.sol.cpp diff --git a/code/virtual_inheritance/trymultiherit.cpp b/exercises/virtual_inheritance/trymultiherit.cpp similarity index 100% rename from code/virtual_inheritance/trymultiherit.cpp rename to exercises/virtual_inheritance/trymultiherit.cpp diff --git a/mlc_config.json b/mlc_config.json new file mode 100644 index 00000000..f9b4ab75 --- /dev/null +++ b/mlc_config.json @@ -0,0 +1,16 @@ +{ + "ignorePatterns": [ + { + "pattern": "https://github.com/issues?.*" + }, + { + "pattern": ".*cookiecutter.*" + }, + { + "pattern": ".*opensource.*" + }, + { + "pattern": "https://github.com/hsf-training/cpluspluscourse/raw/download/.*" + } + ] +} diff --git a/talk/C++Course.tex b/talk/C++Course.tex index 20442ca2..88767298 100644 --- a/talk/C++Course.tex +++ b/talk/C++Course.tex @@ -7,8 +7,19 @@ % setup for the basic/advanced course switch % create a new latex if called basic (false by default) \newif\ifbasic -%\basictrue % uncomment to make basic true -\IfFileExists{onlybasics.tex}{\input{onlybasics}}{} + +% There are three ways to switch to the basic course: +% 1. Uncomment the line below. Disadvantage: git diff won't be empty +%\basictrue +% 2. Tell it to the Makefile. +% 2.1 Invoke "make essentials" +% 2.2 export HEPCPP_ESSENTIALS=1 +% make +\ifdefined\makebasic + \basictrue +\fi +% 3. Create a file named ./buildbasic +\IfFileExists{./buildbasic}{\basictrue} % create a comment environment advanced. depending on the value of basic, we exclude or include it. \ifbasic @@ -17,8 +28,10 @@ % advanced slides have a different structure color \specialcomment{advanced}{ \setbeamercolor{structure}{fg=beamer@blendedblue!40!violet} + \isAdvancedSlidetrue }{ \setbeamercolor{structure}{fg=beamer@blendedblue} + \isAdvancedSlidefalse } \fi @@ -56,8 +69,13 @@ \end{block} \begin{block}{Where to find latest version ?} \begin{itemize} - \item full sources at {\scriptsize \url{https://github.com/hsf-training/cpluspluscourse}} - \item latest pdf under {\scriptsize \href{https://github.com/hsf-training/cpluspluscourse/raw/download/talk/C++Course\_full.pdf}{raw/download/talk/C++Course\_full.pdf}} + \item full sources at \href{https://github.com/hsf-training/cpluspluscourse}{github.com/hsf-training/cpluspluscourse} + \item latest pdf on + \ifbasic + \href{https://github.com/hsf-training/cpluspluscourse/raw/download/talk/C++Course\_essentials.pdf}{GitHub} + \else + \href{https://github.com/hsf-training/cpluspluscourse/raw/download/talk/C++Course\_full.pdf}{GitHub} + \fi \end{itemize} \end{block} \end{frame} @@ -131,7 +149,7 @@ \input{objectorientation/adl} \end{advanced} -\section[More]{Core modern \cpp} +\section[Core]{Core modern \cpp} \input{morelanguage/constness} \begin{advanced} \input{morelanguage/constexpr} @@ -156,11 +174,11 @@ \begin{advanced} \section[exp]{Expert \cpp} + \input{expert/cpp20spaceship} \input{expert/variadictemplate} \input{expert/perfectforwarding} \input{expert/sfinae} \input{expert/cpp20concepts} - \input{expert/cpp20spaceship} \input{expert/modules} \input{expert/coroutines} \end{advanced} diff --git a/talk/Makefile b/talk/Makefile index 55c8d463..7dc47044 100644 --- a/talk/Makefile +++ b/talk/Makefile @@ -1,10 +1,10 @@ all: C++Course.pdf LATEXMK=latexmk -OPTIONS=-pdf -shell-escape -halt-on-error +OPTIONS=-pdf -xelatex -shell-escape -halt-on-error essentials: all -essentials: OPTIONS+=-pretex='\basictrue' +essentials: OPTIONS+=-usepretex='\def\makebasic{}' preview: all preview: OPTIONS+=-pvc @@ -15,6 +15,10 @@ else OPTIONS+=-interaction=nonstopmode endif +ifeq ($(HEPCPP_ESSENTIALS), 1) + OPTIONS+=-usepretex='\def\makebasic{}' +endif + .PHONY: clean clobber FORCE %.pdf: %.tex FORCE diff --git a/talk/basicconcepts/arrayspointers.tex b/talk/basicconcepts/arrayspointers.tex index 74c9f3bd..7a6c4edd 100644 --- a/talk/basicconcepts/arrayspointers.tex +++ b/talk/basicconcepts/arrayspointers.tex @@ -16,7 +16,14 @@ \end{cppcode} \end{frame} -\Scontents*[store-cmd=code_arrays]{ +\begin{frame}[fragile] + \frametitlecpp[98]{Pointers} + \begin{multicols}{2} + % the code has to be repeated n times here. Anyone finding a better solution + % is welcome, but it's not a trivial task, due to the verbatim nature of minted + \begin{overprint}[\columnwidth] + \onslide<1> + \begin{minted}[linenos]{cpp} int i = 4; int *pi = &i; int j = *pi + 1; @@ -32,20 +39,151 @@ // seg fault ! int *pak = (int*)k; int l = *pak; -} -\begin{frame}[fragile] - \frametitlecpp[98]{Pointers} - \begin{multicols}{2} - \begin{overprint}[\columnwidth] - \onslide<1> \highlightCppCode{}{code_arrays} - \onslide<2> \highlightCppCode{1}{code_arrays} - \onslide<3> \highlightCppCode{2}{code_arrays} - \onslide<4> \highlightCppCode{3}{code_arrays} - \onslide<5> \highlightCppCode{5}{code_arrays} - \onslide<6> \highlightCppCode{6}{code_arrays} - \onslide<7> \highlightCppCode{7}{code_arrays} - \onslide<8> \highlightCppCode{8}{code_arrays} - \onslide<9> \highlightCppCode{11}{code_arrays} + \end{minted} + \onslide<2> + \begin{minted}[linenos,highlightlines=1]{cpp} +int i = 4; +int *pi = &i; +int j = *pi + 1; + +int ai[] = {1,2,3}; +int *pai = ai; // decay to ptr +int *paj = pai + 1; +int k = *paj + 1; + +// compile error +int *pak = k; + +// seg fault ! +int *pak = (int*)k; +int l = *pak; + \end{minted} + \onslide<3> + \begin{minted}[linenos,highlightlines=2]{cpp} +int i = 4; +int *pi = &i; +int j = *pi + 1; + +int ai[] = {1,2,3}; +int *pai = ai; // decay to ptr +int *paj = pai + 1; +int k = *paj + 1; + +// compile error +int *pak = k; + +// seg fault ! +int *pak = (int*)k; +int l = *pak; + \end{minted} + \onslide<4> + \begin{minted}[linenos,highlightlines=3]{cpp} +int i = 4; +int *pi = &i; +int j = *pi + 1; + +int ai[] = {1,2,3}; +int *pai = ai; // decay to ptr +int *paj = pai + 1; +int k = *paj + 1; + +// compile error +int *pak = k; + +// seg fault ! +int *pak = (int*)k; +int l = *pak; + \end{minted} + \onslide<5> + \begin{minted}[linenos,highlightlines=5]{cpp} +int i = 4; +int *pi = &i; +int j = *pi + 1; + +int ai[] = {1,2,3}; +int *pai = ai; // decay to ptr +int *paj = pai + 1; +int k = *paj + 1; + +// compile error +int *pak = k; + +// seg fault ! +int *pak = (int*)k; +int l = *pak; + \end{minted} + \onslide<6> + \begin{minted}[linenos,highlightlines=6]{cpp} +int i = 4; +int *pi = &i; +int j = *pi + 1; + +int ai[] = {1,2,3}; +int *pai = ai; // decay to ptr +int *paj = pai + 1; +int k = *paj + 1; + +// compile error +int *pak = k; + +// seg fault ! +int *pak = (int*)k; +int l = *pak; + \end{minted} + \onslide<7> + \begin{minted}[linenos,highlightlines=7]{cpp} +int i = 4; +int *pi = &i; +int j = *pi + 1; + +int ai[] = {1,2,3}; +int *pai = ai; // decay to ptr +int *paj = pai + 1; +int k = *paj + 1; + +// compile error +int *pak = k; + +// seg fault ! +int *pak = (int*)k; +int l = *pak; + \end{minted} + \onslide<8> + \begin{minted}[linenos,highlightlines=8]{cpp} +int i = 4; +int *pi = &i; +int j = *pi + 1; + +int ai[] = {1,2,3}; +int *pai = ai; // decay to ptr +int *paj = pai + 1; +int k = *paj + 1; + +// compile error +int *pak = k; + +// seg fault ! +int *pak = (int*)k; +int l = *pak; + \end{minted} + \onslide<9> + \begin{minted}[linenos,highlightlines=11]{cpp} +int i = 4; +int *pi = &i; +int j = *pi + 1; + +int ai[] = {1,2,3}; +int *pai = ai; // decay to ptr +int *paj = pai + 1; +int k = *paj + 1; + +// compile error +int *pak = k; + +// seg fault ! +int *pak = (int*)k; +int l = *pak; + \end{minted} \end{overprint} \columnbreak \onslide<2->{ @@ -92,7 +230,7 @@ \end{frame} \begin{frame}[fragile] - \frametitlecpp[98]{Dynamic Arrays using C} + \frametitlecpp[98]{Dynamic arrays using C} \begin{cppcode} #include #include @@ -108,7 +246,29 @@ free(ai); // release memory \end{cppcode} - \begin{goodpracticeWithShortcut}{Don't use C's memory management}{C's memory management} + \begin{goodpractice}[C's memory management]{Don't use C's memory management} + Use \cppinline{std::vector} and friends or smart pointers + \end{goodpractice} +\end{frame} + +\begin{frame}[fragile] + \frametitlecpp[98]{Manual dynamic arrays using \cpp} + \begin{cppcode} + #include + #include + + // allocate array of 10 ints + int* ai = new int[10]; // uninitialized + int* ai = new int[10]{}; // zero-initialized + + delete[] ai; // release array memory + + // allocate a single int + int* pi = new int; + int* pi = new int{}; + delete pi; // release scalar memory + \end{cppcode} + \begin{goodpractice}[Manual memory management]{Don't use manual memory management} Use \cppinline{std::vector} and friends or smart pointers - \end{goodpracticeWithShortcut} + \end{goodpractice} \end{frame} diff --git a/talk/basicconcepts/assert.tex b/talk/basicconcepts/assert.tex index 5ad4cbc4..6911443c 100644 --- a/talk/basicconcepts/assert.tex +++ b/talk/basicconcepts/assert.tex @@ -77,7 +77,7 @@ \begin{cppcode*}{} double f(UserType a) { static_assert( - std::is_floating_point::value, + std::is_floating_point_v, // C++17 "This function expects floating-point types."); return std::sqrt(a); } @@ -89,7 +89,7 @@ \textcolor{blue}{a.cpp:3:9:} \textcolor{red}{error:} \textcolor{blue}{static assertion failed: This function} \textcolor{blue}{expects floating-point types.} 2 | static_assert( - | \textcolor{red}{std::is_floating_point::value}, - | \textcolor{red}{~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~} + | \textcolor{red}{std::is_floating_point_v}, + | \textcolor{red}{~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~} \end{Verbatim} \end{frame} diff --git a/talk/basicconcepts/auto.tex b/talk/basicconcepts/auto.tex index f0fc9b5a..331dd76a 100644 --- a/talk/basicconcepts/auto.tex +++ b/talk/basicconcepts/auto.tex @@ -31,7 +31,7 @@ \begin{exerciseWithShortcut}{Loops, references, auto}{Loops, refs, auto} Familiarise yourself with range-based for loops and references \begin{itemize} - \item Go to \texttt{code/loopsRefsAuto} + \item Go to \texttt{exercises/loopsRefsAuto} \item Look at \texttt{loopsRefsAuto.cpp} \item Compile it (\texttt{make}) and run the program (\texttt{./loopsRefsAuto}) \item Work on the tasks that you find in \texttt{loopsRefsAuto.cpp} diff --git a/talk/basicconcepts/classenum.tex b/talk/basicconcepts/classenum.tex index b990f978..aabdcc6c 100644 --- a/talk/basicconcepts/classenum.tex +++ b/talk/basicconcepts/classenum.tex @@ -6,7 +6,7 @@ \center ``members'' grouped together under one name \end{mdframed} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} struct Individual { unsigned char age; float weight; @@ -21,7 +21,7 @@ }; \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=14} + \begin{cppcode*}{firstnumber=14} Individual *ptr = &student; ptr->age = 25; // same as: (*ptr).age = 25; @@ -56,7 +56,7 @@ \center ``members'' packed together at same memory location \end{mdframed} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} union Duration { int seconds; short hours; @@ -104,7 +104,7 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} enum VehicleType { BIKE, // 0 @@ -114,7 +114,7 @@ VehicleType t = CAR; \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=8} + \begin{cppcode*}{firstnumber=8} enum VehicleType : int { // C++11 BIKE = 3, @@ -156,9 +156,9 @@ \end{frame} \begin{frame}[fragile] - \frametitlecpp[98]{More sensible example} + \frametitlecpp[11]{More sensible example} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} enum class ShapeType { Circle, Rectangle @@ -171,7 +171,7 @@ \end{cppcode*} \columnbreak \pause - \begin{cppcode*}{gobble=2,firstnumber=10} + \begin{cppcode*}{firstnumber=10} struct Shape { ShapeType type; union { @@ -183,7 +183,7 @@ \end{multicols} \pause \begin{multicols}{2} - \begin{cppcode*}{gobble=2,firstnumber=17} + \begin{cppcode*}{firstnumber=17} Shape s; s.type = ShapeType::Circle; @@ -191,7 +191,7 @@ \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=20} + \begin{cppcode*}{firstnumber=20} Shape t; t.type = Shapetype::Rectangle; @@ -205,14 +205,14 @@ \frametitle{typedef and using \hfill \cpp98 / \cpp11} Used to create type aliases \begin{alertblock}{\cpp98} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} typedef std::uint64_t myint; myint count = 17; typedef float position[3]; \end{cppcode*} \end{alertblock} \begin{exampleblock}{\cpp11} - \begin{cppcode*}{gobble=2,firstnumber=4} + \begin{cppcode*}{firstnumber=4} using myint = std::uint64_t; myint count = 17; using position = float[3]; diff --git a/talk/basicconcepts/control.tex b/talk/basicconcepts/control.tex index 8786a37f..c2004a0c 100644 --- a/talk/basicconcepts/control.tex +++ b/talk/basicconcepts/control.tex @@ -77,7 +77,7 @@ \begin{frame}[fragile] \frametitlecpp[98]{Control structures: switch} \begin{block}{Syntax} - \begin{cppcode*}{gobble=0} + \begin{cppcode*}{} switch(identifier) { case c1 : statements1; break; case c2 : statements2; break; @@ -134,7 +134,7 @@ switch (c) { case 'a': f(); // Warning emitted - case 'b': // Warning emitted + case 'b': // Warning probably suppressed case 'c': g(); [[fallthrough]]; // Warning suppressed @@ -179,7 +179,7 @@ \end{cppcode*} \vspace{-0.2cm} \begin{itemize} - \item Initializations and increments are comma separated + \item Multiple initializations / increments are comma separated \item Initializations can contain declarations \item Braces are optional if loop body is a single statement \end{itemize} @@ -193,11 +193,11 @@ \end{cppcode*} \end{exampleblock} \pause - \begin{goodpracticeWithShortcut}{Don't abuse the \texttt{for} syntax}{\texttt{for} syntax} + \begin{goodpractice}[\texttt{for} syntax]{Don't abuse the \texttt{for} syntax} \begin{itemize} \item The \cppinline{for} loop head should fit in 1-3 lines \end{itemize} - \end{goodpracticeWithShortcut} + \end{goodpractice} \end{frame} \begin{frame}[fragile] @@ -240,7 +240,7 @@ \end{alertblock} \begin{exampleblock}{\cpp20} \begin{cppcode*}{firstnumber=6} - for (std::size_t i = 0; auto& d : data) { + for (std::size_t i = 0; auto const & d : data) { std::cout << i++ << ' ' << d << '\n'; } \end{cppcode*} @@ -304,7 +304,7 @@ \begin{exerciseWithShortcut}{Control structures}{Control structs} Familiarise yourself with different kinds of control structures. Re-implement them in different ways. \begin{itemize} - \item Go to \texttt{code/control} + \item Go to \texttt{exercises/control} \item Look at \texttt{control.cpp} \item Compile it (\texttt{make}) and run the program (\texttt{./control}) \item Work on the tasks that you find in \texttt{README.md} diff --git a/talk/basicconcepts/coresyntax.tex b/talk/basicconcepts/coresyntax.tex index 97017f05..ce5d7552 100644 --- a/talk/basicconcepts/coresyntax.tex +++ b/talk/basicconcepts/coresyntax.tex @@ -132,7 +132,7 @@ double d = 123'456.789'101; // digit separators, C++14 - double d = 0x4d2.4p3; // hexfloat, 0x4d2.1 * 2^3 + double d = 0x4d2.4p3; // hexfloat, 0x4d2.4 * 2^3 // = 1234.25 * 2^3 = 9874 3.14f, 3.14F, // float @@ -141,8 +141,9 @@ \end{cppcode} \end{frame} +\begin{advanced} \begin{frame}[fragile] - \frametitlecpp[98]{Useful aliases} + \frametitlecpp[11]{Useful aliases} \begin{cppcode} #include // (and others) defines: @@ -160,7 +161,6 @@ \end{cppcode} \end{frame} -\begin{advanced} \begin{frame}[fragile] \frametitlecpp[23]{Extended floating-point types} \begin{block}{Extended floating-point types} @@ -169,16 +169,16 @@ \item Custom conversion and promotion rules \end{itemize} \end{block} - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} #include // may define these: std::float16_t = 3.14f16; // 16 (1+5+10) bit float - std::float32_t = 3.14f32; // like float + std::float32_t = 3.14f32; // like float (1+8+23) // but different type - std::float64_t = 3.14f64; // like double + std::float64_t = 3.14f64; // like double (1+11+52) // but different type std::float128_t = 3.14f128; // 128 (1+15+112) bit float - std::bfloat_t = 3.14bf16; // 16 (1+8+7) bit float + std::bfloat16_t = 3.14bf16; // 16 (1+8+7) bit float // also F16, F32, F64, F128 or BF16 suffix possible \end{cppcode*} diff --git a/talk/basicconcepts/functions.tex b/talk/basicconcepts/functions.tex index a64faf6e..6ef973f5 100644 --- a/talk/basicconcepts/functions.tex +++ b/talk/basicconcepts/functions.tex @@ -3,7 +3,7 @@ \begin{frame}[fragile] \frametitlecpp[98]{Functions} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} // with return type int square(int a) { return a * a; @@ -16,7 +16,7 @@ } \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=11} + \begin{cppcode*}{firstnumber=11} // no return void log(char* msg) { std::cout << msg; @@ -28,12 +28,22 @@ } \end{cppcode*} \end{multicols} + + \pause + + \begin{exampleblock}{Functions and references to returned values} + \begin{cppcode} + int result = square(2); + int & temp = square(2); // Not allowed + int const & temp2 = square(2); // OK + \end{cppcode} + \end{exampleblock} \end{frame} \begin{frame}[fragile] \frametitlecpp[98]{Function default arguments} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} // must be the trailing // argument int add(int a, @@ -45,7 +55,7 @@ \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=11} + \begin{cppcode*}{firstnumber=11} // multiple default // arguments are possible int add(int a = 2, @@ -59,7 +69,14 @@ \end{frame} -\Scontents*[store-cmd=code_bigStruct]{ +\begin{frame}[fragile] + \frametitlecpp[98]{Functions: parameters are passed by value} + \begin{multicols}{2} + % the code has to be repeated n times here. Anyone finding a better solution + % is welcome, but it's not a trivial task, due to the verbatim nature of minted + \begin{overprint}[\columnwidth] + \onslide<1-2> + \begin{minted}[linenos,highlightlines=2]{cpp} struct BigStruct {...}; BigStruct s; @@ -74,17 +91,41 @@ ... } printRef(s); // no copy -} -\begin{frame}[fragile] - \frametitlecpp[98]{Functions: parameters are passed by value} - \begin{multicols}{2} - \begin{overprint}[\columnwidth] - \onslide<1-2> - \highlightCppCode{2}{code_bigStruct} + \end{minted} \onslide<3> - \highlightCppCode{5,8}{code_bigStruct} + \begin{minted}[linenos,highlightlines={5,8}]{cpp} +struct BigStruct {...}; +BigStruct s; + +// parameter by value +void printVal(BigStruct p) { + ... +} +printVal(s); // copy + +// parameter by reference +void printRef(BigStruct &q) { + ... +} +printRef(s); // no copy + \end{minted} \onslide<4-> - \highlightCppCode{11,14}{code_bigStruct} + \begin{minted}[linenos,highlightlines={11,14}]{cpp} +struct BigStruct {...}; +BigStruct s; + +// parameter by value +void printVal(BigStruct p) { + ... +} +printVal(s); // copy + +// parameter by reference +void printRef(BigStruct &q) { + ... +} +printRef(s); // no copy + \end{minted} \end{overprint} \columnbreak \null \vfill @@ -111,7 +152,14 @@ \end{multicols} \end{frame} -\Scontents*[store-cmd=code_smallStruct]{ +\begin{frame}[fragile] + \frametitlecpp[98]{Functions: pass by value or reference?} + \begin{multicols}{2} + % the code has to be repeated n times here. Anyone finding a better solution + % is welcome, but it's not a trivial task, due to the verbatim nature of minted + \begin{overprint}[\columnwidth] + \onslide<1> + \begin{minted}[linenos]{cpp} struct SmallStruct {int a;}; SmallStruct s = {1}; @@ -126,27 +174,126 @@ } changeRef(s); // s.a == 2 -} -\begin{frame}[fragile] - \frametitlecpp[98]{Functions: pass by value or reference?} - \begin{multicols}{2} - \begin{overprint}[\columnwidth] - \onslide<1> - \highlightCppCode{}{code_smallStruct} + \end{minted} \onslide<2> - \highlightCppCode{2}{code_smallStruct} + \begin{minted}[linenos,highlightlines=2]{cpp} +struct SmallStruct {int a;}; +SmallStruct s = {1}; + +void changeVal(SmallStruct p) { + p.a = 2; +} +changeVal(s); +// s.a == 1 + +void changeRef(SmallStruct &q) { + q.a = 2; +} +changeRef(s); +// s.a == 2 + \end{minted} \onslide<3> - \highlightCppCode{4,7}{code_smallStruct} + \begin{minted}[linenos,highlightlines={4,7}]{cpp} +struct SmallStruct {int a;}; +SmallStruct s = {1}; + +void changeVal(SmallStruct p) { + p.a = 2; +} +changeVal(s); +// s.a == 1 + +void changeRef(SmallStruct &q) { + q.a = 2; +} +changeRef(s); +// s.a == 2 + \end{minted} \onslide<4> - \highlightCppCode{5}{code_smallStruct} + \begin{minted}[linenos,highlightlines=5]{cpp} +struct SmallStruct {int a;}; +SmallStruct s = {1}; + +void changeVal(SmallStruct p) { + p.a = 2; +} +changeVal(s); +// s.a == 1 + +void changeRef(SmallStruct &q) { + q.a = 2; +} +changeRef(s); +// s.a == 2 + \end{minted} \onslide<5> - \highlightCppCode{8}{code_smallStruct} + \begin{minted}[linenos,highlightlines=8]{cpp} +struct SmallStruct {int a;}; +SmallStruct s = {1}; + +void changeVal(SmallStruct p) { + p.a = 2; +} +changeVal(s); +// s.a == 1 + +void changeRef(SmallStruct &q) { + q.a = 2; +} +changeRef(s); +// s.a == 2 + \end{minted} \onslide<6> - \highlightCppCode{10,13}{code_smallStruct} + \begin{minted}[linenos,highlightlines={10,13}]{cpp} +struct SmallStruct {int a;}; +SmallStruct s = {1}; + +void changeVal(SmallStruct p) { + p.a = 2; +} +changeVal(s); +// s.a == 1 + +void changeRef(SmallStruct &q) { + q.a = 2; +} +changeRef(s); +// s.a == 2 + \end{minted} \onslide<7> - \highlightCppCode{11}{code_smallStruct} + \begin{minted}[linenos,highlightlines=11]{cpp} +struct SmallStruct {int a;}; +SmallStruct s = {1}; + +void changeVal(SmallStruct p) { + p.a = 2; +} +changeVal(s); +// s.a == 1 + +void changeRef(SmallStruct &q) { + q.a = 2; +} +changeRef(s); +// s.a == 2 + \end{minted} \onslide<8> - \highlightCppCode{14}{code_smallStruct} + \begin{minted}[linenos,highlightlines=14]{cpp} +struct SmallStruct {int a;}; +SmallStruct s = {1}; + +void changeVal(SmallStruct p) { + p.a = 2; +} +changeVal(s); +// s.a == 1 + +void changeRef(SmallStruct &q) { + q.a = 2; +} +changeRef(s); +// s.a == 2 + \end{minted} \end{overprint} \columnbreak \null \vfill @@ -205,14 +352,16 @@ \begin{block}{Overloading} \begin{itemize} \item We can have multiple functions with the same name - \item They must have different parameter lists - \item A different return type alone is not allowed + \begin{itemize} + \item Must have different parameter lists + \item A different return type alone is not allowed + \item Form a so-called ``overload set'' + \end{itemize} \item Default arguments can cause ambiguities - \item All functions with the same name form an ``overload set'' \end{itemize} \end{block} \begin{exampleblock}{} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} int sum(int b); // 1 int sum(int b, int c); // 2, ok, overload // float sum(int b, int c); // disallowed @@ -230,7 +379,7 @@ \begin{exercise}{Functions} Familiarise yourself with pass by value / pass by reference. \begin{itemize} - \item Go to \texttt{code/functions} + \item Go to \texttt{exercises/functions} \item Look at \texttt{functions.cpp} \item Compile it (\texttt{make}) and run the program (\texttt{./functions}) \item Work on the tasks that you find in \texttt{functions.cpp} @@ -250,10 +399,10 @@ \end{itemize} \end{goodpractice} \begin{exampleblock}{Example: Good} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} /// Count number of dilepton events in data. /// \param d Dataset to search. - unsigned int countDileptons(Data d) { + unsigned int countDileptons(Data &d) { selectEventsWithMuons(d); selectEventsWithElectrons(d); return d.size(); @@ -264,7 +413,7 @@ \begin{onlyenv}<2-> \begin{alertblock}{Example: don't! Everything in one long function} \begin{multicols}{2} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} unsigned int runJob() { // Step 1: data Data data; @@ -281,7 +430,7 @@ for (....) { \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=6,firstnumber=last} + \begin{cppcode*}{firstnumber=last} if (...) { data.erase(...); } diff --git a/talk/basicconcepts/headersinterfaces.tex b/talk/basicconcepts/headersinterfaces.tex index e4d120d4..84e8ba57 100644 --- a/talk/basicconcepts/headersinterfaces.tex +++ b/talk/basicconcepts/headersinterfaces.tex @@ -41,12 +41,12 @@ #endif \end{cppcode} \pause - \begin{goodpracticeWithShortcut}{Use preprocessor only in very restricted cases}{preprocessor} + \begin{goodpractice}[preprocessor]{Use preprocessor only in very restricted cases} \begin{itemize} \item Conditional inclusion of headers \item Customization for specific compilers/platforms \end{itemize} - \end{goodpracticeWithShortcut} + \end{goodpractice} \end{frame} \begin{frame}[fragile] @@ -73,3 +73,23 @@ \end{cppcode*} \end{block} \end{frame} + +\begin{frame}[fragile] + \frametitlecpp[98]{Header / source separation} + \begin{goodpractice}[Header / source]{Headers and source files} + \begin{itemize} + \item Headers should contain declarations of functions / classes + \begin{itemize} + \item Only create them if interface is used somewhere else + \end{itemize} + \item Might be included/compiled many times + \item Good to keep them short + \item Minimise \cppinline{#include} statements + \item Put long code in implementation files. Exceptions: + \begin{itemize} + \item Short functions + \item Templates and constexpr functions + \end{itemize} + \end{itemize} + \end{goodpractice} +\end{frame} diff --git a/talk/basicconcepts/operators.tex b/talk/basicconcepts/operators.tex index 1ea2f8af..e9834e83 100644 --- a/talk/basicconcepts/operators.tex +++ b/talk/basicconcepts/operators.tex @@ -56,6 +56,7 @@ bool d = (4 <= 4); // true bool e = (4 > 4); // false bool f = (4 >= 4); // true + auto g = (5 <=> 5); // C++20 (later) \end{cppcode*} \end{block} \pause diff --git a/talk/basicconcepts/references.tex b/talk/basicconcepts/references.tex index a8fa13c8..40e775fc 100644 --- a/talk/basicconcepts/references.tex +++ b/talk/basicconcepts/references.tex @@ -11,7 +11,7 @@ \end{block} \begin{exampleblock}{Example:} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} int i = 2; int &iref = i; // access to i iref = 3; // i is now 3 diff --git a/talk/basicconcepts/scopesnamespaces.tex b/talk/basicconcepts/scopesnamespaces.tex index 1bea11f2..c49976bd 100644 --- a/talk/basicconcepts/scopesnamespaces.tex +++ b/talk/basicconcepts/scopesnamespaces.tex @@ -21,15 +21,6 @@ \end{exampleblock} \end{frame} -\Scontents*[store-cmd=code_scopes]{ -int a = 1; -{ - int b[4]; - b[0] = a; -} -// Doesn't compile here: -// b[1] = a + 1; -} \begin{frame}[fragile] \frametitlecpp[98]{Scope and lifetime of variables} \begin{block}{Variable life time} @@ -45,15 +36,49 @@ \end{itemize} \end{goodpractice} \begin{multicols}{2} + % the code has to be repeated n times here. Anyone finding a better solution + % is welcome, but it's not a trivial task, due to the verbatim nature of minted \begin{overprint}[\columnwidth] \onslide<1> - \highlightCppCode{1}{code_scopes} + \begin{minted}[linenos,highlightlines=1]{cpp} +int a = 1; +{ + int b[4]; + b[0] = a; +} +// Doesn't compile here: +// b[1] = a + 1; + \end{minted} \onslide<2> - \highlightCppCode{3}{code_scopes} + \begin{minted}[linenos,highlightlines=3]{cpp} +int a = 1; +{ + int b[4]; + b[0] = a; +} +// Doesn't compile here: +// b[1] = a + 1; + \end{minted} \onslide<3> - \highlightCppCode{4}{code_scopes} + \begin{minted}[linenos,highlightlines=4]{cpp} +int a = 1; +{ + int b[4]; + b[0] = a; +} +// Doesn't compile here: +// b[1] = a + 1; + \end{minted} \onslide<4> - \highlightCppCode{7}{code_scopes} + \begin{minted}[linenos,highlightlines=7]{cpp} +int a = 1; +{ + int b[4]; + b[0] = a; +} +// Doesn't compile here: +// b[1] = a + 1; + \end{minted} \end{overprint} \columnbreak @@ -97,7 +122,7 @@ \item They can be embedded to create hierarchies (separator is '::') \end{itemize} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} int a; namespace n { int a; // no clash @@ -113,7 +138,7 @@ } \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=14} + \begin{cppcode*}{firstnumber=14} namespace p { // reopen p void f() { p::a = 6; @@ -134,8 +159,7 @@ \begin{frame}[fragile] \frametitlecpp[17]{Nested namespaces} - Easier way to declare nested namespaces - \begin{alertblock}{\cpp98} + \begin{alertblock}{\cpp98: Old way to declare nested namespaces} \begin{cppcode*}{} namespace A { namespace B { @@ -146,13 +170,18 @@ } \end{cppcode*} \end{alertblock} - \begin{exampleblock}{\cpp17} + \begin{exampleblock}{\cpp17: Nested declaration} \begin{cppcode*}{} namespace A::B::C { //... } \end{cppcode*} \end{exampleblock} + \begin{exampleblock}{\cpp17: Namespace alias} + \begin{cppcode*}{} + namespace ABC = A::B::C; + \end{cppcode*} + \end{exampleblock} \end{frame} \begin{advanced} @@ -178,9 +207,38 @@ \end{itemize} \end{block} \begin{alertblock}{Supersedes static} - \begin{cppcode*}{gobble=2,firstnumber=4} + \begin{cppcode*}{firstnumber=4} static int localVar; // equivalent C code \end{cppcode*} \end{alertblock} \end{frame} + +\begin{frame}[fragile] + \frametitlecpp[98]{Using namespace directives} + \begin{alertblock}{Avoid ``using namespace'' directives} + \begin{itemize} + \item Make all members of a namespace visible in current scope + \item Risk of name clashes or ambiguities + \end{itemize} + \begin{cppcode*}{} + using namespace std; + cout << "We can print now\n"; // uses std::cout + \end{cppcode*} + \end{alertblock} + \begin{alertblock}{Never use in headers at global scope!} + \begin{cppcode*}{} + #include "PoorlyWritten.h" // using namespace std; + struct array { ... }; + array a; // Error: name clash with std::array + \end{cppcode*} + \end{alertblock} + \begin{block}{What to do instead} + \begin{itemize} + \item Qualify names: \cppinline{std::vector}, \cppinline{std::cout}, \ldots + \item Put things that belong together in the same namespace + \item Use \textit{using declarations} in local scopes: \cppinline{using std::cout;} + \end{itemize} + \end{block} +\end{frame} + \end{advanced} diff --git a/talk/concurrency/atomic.tex b/talk/concurrency/atomic.tex index ae3c7062..0580a041 100644 --- a/talk/concurrency/atomic.tex +++ b/talk/concurrency/atomic.tex @@ -96,7 +96,7 @@ \end{itemize} \end{block} \begin{exampleblock}{} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} int a{0}; std::thread t1([&]{ std::atomic_ref r{a}; r++;}); std::thread t2([&]{ std::atomic_ref{a}++; }); @@ -106,7 +106,7 @@ \end{cppcode*} \end{exampleblock} \begin{alertblock}{Don't mix concurrent atomic and non-atomic access} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} std::thread t3([&]{ std::atomic_ref{a}++; }); a += 2; // data race t3.join(); @@ -118,7 +118,7 @@ \frametitle{Atomic types in \cpp} \begin{exercise}{Atomics} \begin{itemize} - \item Go to \texttt{code/atomic} + \item Go to \texttt{exercises/atomic} \item You'll find a program with the same race condition as in \texttt{race} \item Fix it using \cppinline{std::atomic} \end{itemize} diff --git a/talk/concurrency/condition.tex b/talk/concurrency/condition.tex index 07eadb06..d688408a 100644 --- a/talk/concurrency/condition.tex +++ b/talk/concurrency/condition.tex @@ -60,26 +60,28 @@ \onslide<1> \begin{block}{Mechanics of wait} \begin{itemize} - \item Many threads are waiting for shared data - \item Pass a \cppinline{unique_lock} and a predicate for wakeup to \cppinline{wait()} - \item \cppinline{wait()} sends threads to sleep while predicate is \cppinline{false} + \item Many threads can wait for shared data + \item Pass \cppinline{unique_lock} and optional predicate to \cppinline{wait()} + \item \cppinline{wait()} keeps threads asleep while predicate is \cppinline{false} + \begin{itemize} + \item Threads might wake up spuriously, but \cppinline{wait()} returns only when lock available \emph{and} predicate \cppinline{true} + \end{itemize} \item \cppinline{wait()} will only lock when necessary; unlocked while sleeping - \item Threads might wake up spuriously, but \cppinline{wait()} returns only when lock available \emph{and} predicate \cppinline{true} \end{itemize} \end{block} \onslide<2-> \begin{block}{Waiting / waking up} \begin{itemize} - \item \cppinline{notify_all()} is called, threads wake up + \item When \cppinline{notify_all()} is called, threads wake up \item Threads try to lock mutex, and evaluate predicate - \item One thread succeeds to acquire mutex, starts data processing - \item {\color{red} Problem}: Thread holds mutex now, other threads are blocked! + \item One thread succeeds to acquire mutex, starts processing data + \item {\color{red} Problem}: One thread holds mutex, other threads are blocked! \end{itemize} \end{block} \end{overprint} \begin{alertblock}{Na\"ive waiting} - \begin{cppcode*}{gobble=2,highlightlines=3} + \begin{cppcode*}{highlightlines=3} auto processData = [&](){ std::unique_lock lock{mutex}; cond.wait(lock, [&](){ return data.isReady(); }); @@ -99,7 +101,7 @@ \end{block} \begin{exampleblock}{Correct waiting} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} auto processData = [&](){ { std::unique_lock lock{mutex}; @@ -117,7 +119,7 @@ \frametitle{Condition variables} \begin{exerciseWithShortcut}{Condition variables}{Condition vars} \begin{itemize} - \item Go to code/condition\_variable + \item Go to \texttt{exercises/condition\_variable} \item Look at the code and run it\\ See that it has a race condition \item Fix the race condition in the usage of the condition variable diff --git a/talk/concurrency/mutexes.tex b/talk/concurrency/mutexes.tex index e35861f6..5721e8d6 100644 --- a/talk/concurrency/mutexes.tex +++ b/talk/concurrency/mutexes.tex @@ -18,7 +18,7 @@ \end{cppcode*} \end{exampleblockGB} \pause - \begin{block}{What do you expect? (Exercise code/race)} + \begin{block}{What do you expect? (Exercise exercises/race)} \pause Anything between 100 and 200 !!! \end{block} @@ -114,13 +114,19 @@ \frametitlecpp[17]{Mutexes and Locks} \begin{goodpractice}{Locking} \begin{itemize} - \item Generally, use \cppinline{std::scoped_lock}. Before \cpp17 use \cppinline{std::lock_guard}. - \item Hold as short as possible, consider wrapping critical section in block statement \cppinline|{ }| - \item Only if manual control needed, use \cppinline{std::unique_lock} + \item Generally, use \cppinline{std::scoped_lock} + \begin{itemize} + \item Before \cpp17 use \cppinline{std::lock_guard}. + \end{itemize} + \item Hold as short as possible + \begin{itemize} + \item Consider wrapping critical section in block statement \cppinline|{ }| + \end{itemize} + \item Only if manual control needed, use \cppinline{std::unique_lock} \end{itemize} \end{goodpractice} \begin{exampleblock}{} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} void function(...) { // uncritical work ... { @@ -137,7 +143,7 @@ \frametitle{Mutexes and Locks} \begin{exerciseWithShortcut}{Mutexes and Locks}{Mutexes/Locks} \begin{itemize} - \item Go to code/race + \item Go to \texttt{exercises/race} \item Look at the code and try it\\ See that it has a race condition \item Use a mutex to fix the issue @@ -204,7 +210,7 @@ \end{itemize} \end{block} \begin{exampleblock}{} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} Data data; std::shared_mutex mutex; auto reader = [&](){ std::shared_lock lock{mutex}; @@ -219,3 +225,25 @@ \end{cppcode*} \end{exampleblock} \end{frame} + +\begin{frame}[fragile] + \frametitlecpp[20]{Synchronised \texttt{cout}} + \begin{block}{Parallel writing to streams} + \begin{itemize} + \item Parallel writes to streams become garbled when not synchronised + \item \cppinline{std::osyncstream} wraps and synchronises output to streams + \item Multiple instances of the osyncstream synchronise globally + \end{itemize} + \end{block} + \begin{exampleblock}{} + \begin{cppcode*}{} + void worker(const int id, std::ostream & os); + void func() { + std::osyncstream synccout1{std::cout}; + std::osyncstream synccout2{std::cout}; + std::jthread t1{worker, 0, std::ref(synccout1)}; + std::jthread t2{worker, 1, std::ref(synccout2)}; + } + \end{cppcode*} + \end{exampleblock} +\end{frame} diff --git a/talk/concurrency/threadlocal.tex b/talk/concurrency/threadlocal.tex index 64bbe570..0473a91f 100644 --- a/talk/concurrency/threadlocal.tex +++ b/talk/concurrency/threadlocal.tex @@ -16,10 +16,11 @@ \begin{exampleblock}{} \begin{cppcode*}{} thread_local int a{0}; - std::jthread t1([&] { a++; }); - std::jthread t2([&] { a++; }); - a += 2; - t1.join(); t2.join(); + { + std::jthread t1([&] { a++; }); + std::jthread t2([&] { a++; }); + a += 2; + } assert( a == 2 ); // Guaranteed to succeed \end{cppcode*} \end{exampleblock} diff --git a/talk/concurrency/threadsasync.tex b/talk/concurrency/threadsasync.tex index d80ee2fb..26a16357 100644 --- a/talk/concurrency/threadsasync.tex +++ b/talk/concurrency/threadsasync.tex @@ -1,50 +1,34 @@ \subsection[thr]{Threads and async} \begin{frame}[fragile] - \frametitle{Basic concurrency \hfill \cpp11/\cpp20} + \frametitlecpp[11]{Basic concurrency} \begin{block}{Threading} \begin{itemize} \item \cpp11 added \cppinline{std::thread} in \cppinline{} header \item takes a function as argument of its constructor \item must be detached or joined before the main thread terminates - \item \cpp20: \cppinline{std::jthread} automatically joins at destruction \end{itemize} \end{block} - \vspace{-1\baselineskip} - \begin{overprint} - \onslide<1> - \begin{exampleblock}{Example code} - \begin{cppcode*}{gobble=2} - void foo() {...} - void bar() {...} - int main() { - std::thread t1{foo}; - std::thread t2{bar}; - for (auto t: {&t1,&t2}) t->join(); - return 0; - } - \end{cppcode*} - \end{exampleblock} - \onslide<2> - \begin{exampleblock}{Example with jthread (\cpp20)} - \begin{cppcode*}{gobble=2} - void foo() {...} - void bar() {...} - int main() { - std::jthread t1{foo}; - std::jthread t2{bar}; - return 0; - } - \end{cppcode*} - \end{exampleblock} - \end{overprint} + + \begin{exampleblock}{Example code} + \begin{cppcode*}{} + void foo() {...} + void bar() {...} + int main() { + std::thread t1{foo}; + std::thread t2{bar}; + for (auto t: {&t1,&t2}) t->join(); + return 0; + } + \end{cppcode*} + \end{exampleblock} \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{The thread constructor} \begin{exampleblock}{Can take a function and its arguments} \begin{cppcode*}{} - void function(int j, double j) {...}; + void function(int j, double j) {...} std::thread t1{function, 1, 2.0}; \end{cppcode*} \end{exampleblock} @@ -64,6 +48,57 @@ \end{exampleblock} \end{frame} +\begin{frame}[fragile] + \frametitlecpp[20]{\texttt{std::jthread}} + \begin{goodpractice}[jthread vs.\ thread]{Prefer \cppinline{std::jthread} if \cpp20 is available} + \begin{itemize} + \item In \cppinline{} header since \cpp20 + \item Automatically joins on destruction + \item Can be stopped from outside + \end{itemize} + \end{goodpractice} + + \begin{exampleblock}{Example with jthread} + \begin{cppcode*}{} + void foo() {...} + void bar() {...} + int main() { + std::jthread t1{foo}; + std::jthread t2{bar}; + // No join required + return 0; + } + \end{cppcode*} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile] + \frametitlecpp[20]{Using jthread's stop\_token} + \begin{block}{} + \begin{itemize} + \item Like \cppinline{std::thread}, \cppinline{jthread} can take a function or functors/lambdas and their arguments + \item New: Can take functions that handle \cppinline{stop_token}s + \end{itemize} + \end{block} + \begin{exampleblock}{stop\_token in a function} + \begin{cppcode*}{} + void function(std::stop_token st, int j) { + while (!st.stop_requested()) { + // ... + }} + std::jthread t1{function, 1}; + t1.request_stop(); + \end{cppcode*} + \end{exampleblock} + \begin{exampleblock}{A lambda with a stop\_token} + \begin{cppcode*}{} + std::jthread t1{ + [](std::stop_token st, int i){ ... }, 1}; + \end{cppcode*} + \end{exampleblock} +\end{frame} + + \begin{frame}[fragile] \frametitlecpp[11]{Basic asynchronicity} \begin{block}{Concept} @@ -106,13 +141,13 @@ \end{itemize} \end{block} \pause - \begin{exampleblock}{Usage} + \begin{exampleblockGB}{Usage}{https://godbolt.org/z/oMGz5jhMr}{\texttt{std::async}} \begin{cppcode*}{} int f() {...} auto res1 = std::async(std::launch::async, f); auto res2 = std::async(std::launch::deferred, f); \end{cppcode*} - \end{exampleblock} + \end{exampleblockGB} \end{frame} \begin{frame}[fragile] diff --git a/talk/expert/coroutines.tex b/talk/expert/coroutines.tex index 0146a6d0..dc19993e 100644 --- a/talk/expert/coroutines.tex +++ b/talk/expert/coroutines.tex @@ -5,14 +5,14 @@ \subsection{Coroutines} \begin{block}{The generator use case (one of several)} In python one can write {\scriptsize - \begin{pythoncode*}{gobble=2} + \begin{pythoncode*}{} for i in range(0, 10): print(i) \end{pythoncode*} } What about it in \cpp? {\scriptsize - \begin{cppcode*}{gobble=2, firstnumber=3} + \begin{cppcode*}{firstnumber=3} someType range(int first, int last) { ... } for (auto i : range(0, 10)) { std::cout << i << "\n"; @@ -35,7 +35,7 @@ \subsection{Coroutines} \frametitlecpp[20]{What \cpp20 allows} \begin{exampleblock}{Interesting part of the implementation (see \href{https://en.cppreference.com/w/cpp/coroutine/coroutine\_handle\#Example}{\color{blue!50!white}cppreference})} {\scriptsize - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template Generator range(T first, const T last) { while (first < last) { @@ -92,7 +92,7 @@ \subsection{Coroutines} \frametitlecpp[20]{Minimal code} \begin{block}{Definition of an empty coroutine} {\scriptsize - \begin{cppcode*}{gobble=2, firstnumber=3} + \begin{cppcode*}{firstnumber=3} #include struct Task { struct promise_type { @@ -184,7 +184,7 @@ \subsection{Coroutines} \end{block} \begin{exampleblock}{Code} {\scriptsize - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} Task myCoroutine() { std::cout << "Step 1 of coroutine\n"; co_await std::suspend_always{}; @@ -214,7 +214,7 @@ \subsection{Coroutines} \end{block} \begin{exampleblock}{Code} {\scriptsize - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} struct Task { struct promise_type { Task get_return_object() { @@ -234,8 +234,8 @@ \subsection{Coroutines} \begin{frame}[fragile] \frametitlecpp[20]{Resuming a coroutine} \scriptsize - \begin{exampleblockGB}{User code}{https://godbolt.org/z/qx46Pa4v3}{Resuming a coroutine} - \begin{cppcode*}{gobble=2} + \begin{exampleblockGB}{User code}{https://godbolt.org/z/qx46Pa4v3}{Coroutine resumption} + \begin{cppcode*}{} Task myCoroutine() { std::cout << "Step 1 of coroutine\n"; co_await std::suspend_always{}; @@ -254,7 +254,7 @@ \subsection{Coroutines} \end{cppcode*} \end{exampleblockGB} \begin{block}{} - \begin{minted}[gobble=4]{text} + \begin{minted}{text} Step 1 of coroutine In main, between Step 1 and 2 Step 2 of coroutine @@ -278,7 +278,7 @@ \subsection{Coroutines} \begin{itemize} \item which requires another piece of code in \cppinline{Task}, e.g. {\scriptsize - \begin{cppcode*}{gobble=8,linenos=false} + \begin{cppcode*}{linenos=false} ~Task() { if (h_) h_.destroy(); } \end{cppcode*} } @@ -304,7 +304,7 @@ \subsection{Coroutines} \end{block} \begin{exampleblock}{Typical code} {\scriptsize - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} template struct Task { struct promise_type { T value_; @@ -325,7 +325,7 @@ \subsection{Coroutines} \frametitlecpp[20]{\texttt{co\_yield} - generator behavior} \begin{block}{Generator behavior - more boiler plate code} {\scriptsize - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} template struct Task { struct iterator { @@ -352,7 +352,7 @@ \subsection{Coroutines} \frametitlecpp[20]{\texttt{co\_yield} - final usage} \begin{exampleblockGB}{Finally, we can use the generator nicely}{https://godbolt.org/z/6bbrYrss5}{\texttt{co\_yield}} {\scriptsize - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} Task range(int first, int last) { while (first != last) { co_yield first++; @@ -371,7 +371,7 @@ \subsection{Coroutines} \item we should pause in \cppinline{initial_suspend} \end{itemize} {\scriptsize - \begin{cppcode*}{gobble=4,firstnumber=10} + \begin{cppcode*}{firstnumber=10} struct Task { struct promise_type { ... @@ -387,7 +387,7 @@ \subsection{Coroutines} \begin{frame}[fragile] \frametitlecpp[20]{Laziness allows for infinite ranges} \begin{exampleblockGB}{Generators do not need to be finite}{https://godbolt.org/z/MP6qdGWYe}{Infinite generator} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} Task range(int first) { while (true) { co_yield first++; @@ -404,7 +404,7 @@ \subsection{Coroutines} \frametitlecpp[20]{Real life example} \begin{exampleblockGB}{A Fibonacci generator, from \cpprefLink{https://en.cppreference.com/w/cpp/language/coroutines}}{https://godbolt.org/z/3Pev1M8se}{Fibonacci generator} \scriptsize - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} Task fibonacci(unsigned n) { if (n==0) co_return; co_yield 0; @@ -497,7 +497,7 @@ \subsection{Coroutines} \frametitlecpp[20]{\texttt{awaiter} example} \begin{exampleblockGB}{Switch to new thread, from \cpprefLink{https://en.cppreference.com/w/cpp/language/coroutines}}{https://godbolt.org/z/K7svE4P7s}{Thread switching} \scriptsize - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} auto switch_to_new_thread(std::jthread& out) { struct awaiter { std::jthread* th; @@ -531,7 +531,7 @@ \subsection{Coroutines} \raggedright \begin{block}{Output} \scriptsize - \begin{minted}[gobble=9]{text} + \begin{minted}{text} Started on thread: 140144191063872 New thread ID: 140144191059712 Resumed on thread: 140144191059712 diff --git a/talk/expert/cpp20concepts.tex b/talk/expert/cpp20concepts.tex index 3b1631a9..f77a9657 100644 --- a/talk/expert/cpp20concepts.tex +++ b/talk/expert/cpp20concepts.tex @@ -14,7 +14,7 @@ \begin{frame}[fragile] \frametitlecpp[17]{The world before concepts} - \begin{block}{\cpp17 work around : SFINAE} + \begin{block}{\cpp17 work around: SFINAE} \begin{itemize} \item Unsuited arguments can be avoided by inserting fake template arguments, leading to a substitution failure @@ -27,7 +27,7 @@ >> bool equal( T e1, T e2 ) { - return std::abs(e1-e2)::epsilon(); + return std::abs(e1-e2) < std::numeric_limits::epsilon(); } ... equal(10,5+5) ... \end{cppcode*} @@ -62,7 +62,7 @@ template requires std::is_floating_point_v bool equal( T e1, T e2 ) { - return std::abs(e1-e2)::epsilon(); + return std::abs(e1-e2) < std::numeric_limits::epsilon(); } ... equal(10,5+5) ... \end{cppcode*} @@ -115,11 +115,11 @@ \end{block} \begin{exampleblock}{A new keyword: \texttt{concept}} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template< typename T> concept MyFloatingPoint = std::is_floating_point_v && - std::numeric_limits::epsilon()>0; + std::numeric_limits::epsilon() > 0; template requires MyFloatingPoint @@ -133,12 +133,12 @@ \frametitlecpp[20]{Some usages of concepts} \begin{block}{Constrained template parameters} \begin{itemize} - \item a concept can replace \cppinline{typename} in a template parameter lists + \item a concept can replace \cppinline{typename} in a template parameter list \end{itemize} \end{block} \begin{exampleblock}{} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template bool equal( T e1, T e2 ) { return std::abs(e1-e2)::epsilon(); @@ -151,7 +151,7 @@ \end{itemize} \end{block} \begin{exampleblock}{Example code} - \begin{cppcode*}{firstnumber=4,gobble=2} + \begin{cppcode*}{firstnumber=4} MyFloatingPoint auto f = 3.14f; MyFloatingPoint auto d = 3.14; MyFloatingPoint auto i = 3; // compile error @@ -175,7 +175,7 @@ \end{block} \begin{exampleblock}{} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} // no template <...> here! bool equal( MyFloatingPoint auto e1, MyFloatingPoint auto e2 ) { @@ -199,7 +199,7 @@ \end{block} \begin{exampleblock}{E.g.: the floating point concept} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} #include bool equal( std::floating_point auto e1, std::floating_point auto e2 ) { @@ -223,7 +223,7 @@ \end{block} \begin{exampleblock}{Using concepts with \texttt{if constexpr}} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template bool equal( T e1, T e2 ) { @@ -250,7 +250,7 @@ \end{block} \begin{exampleblock}{Practically} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template concept StreamableAndComparableNumber = requires( T v1, T v2, std::ostream os ) { @@ -261,7 +261,7 @@ \end{cppcode*} \end{exampleblock} \begin{block}{} - Remember : use standard concepts first + Remember: use standard concepts first \end{block} \end{frame} @@ -283,7 +283,7 @@ \frametitlecpp[20]{Exercise time} \begin{exercise}{Concepts} \begin{itemize} - \item go to code/concepts + \item go to \texttt{exercises/concepts} \item you will use concepts to optimize some loop on iterators \item follow the instructions in the source code \end{itemize} diff --git a/talk/expert/modules.tex b/talk/expert/modules.tex index cc32e014..5ff95577 100644 --- a/talk/expert/modules.tex +++ b/talk/expert/modules.tex @@ -7,9 +7,9 @@ \item Modules allow for sharing declarations and definitions across translation units \item Header files do the same, but modules have benefits: \begin{itemize} - \item Better isolation. No cross-talk between multiple pieces of included code, and also not between included code and your code. + \item Better isolation. No cross-talk between multiple pieces of included code, or between included code and your code. \item Better control over public interface of your library. Avoid leaking library dependencies into user code. - \item Faster compilation + \item Faster compilation (\href{https://youtu.be/DJTEUFRslbI?si=ZRvH1wx0sVJVriLh&t=3259}{import fmt; \textgreater1500x faster}) \end{itemize} \end{itemize} \end{block} @@ -28,7 +28,7 @@ \begin{frame}[fragile] \frametitlecpp[20]{Writing modules} \begin{exampleblock}{ratio.cpp} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} export module math; import ; namespace utils { // ns. and module names orthogonal @@ -39,7 +39,7 @@ \end{cppcode*} \end{exampleblock} \begin{exampleblock}{main.cpp} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} import ; import math; int main() { @@ -66,7 +66,7 @@ \begin{frame}[fragile] \frametitlecpp[20]{Writing modules} \begin{exampleblock}{What to export} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} export module math; export void f(); // function @@ -89,7 +89,7 @@ \begin{frame}[fragile] \frametitlecpp[20]{Writing modules} \begin{alertblock}{What not to export} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} export module math; namespace { @@ -110,23 +110,23 @@ \begin{frame}[fragile] \frametitlecpp[20]{Visibility and reachability} \begin{exampleblock}{ratio.cpp} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} export module math; struct Ratio { float num, denom; }; export Ratio golden() { ... } // golden is visible \end{cppcode*} \end{exampleblock} \begin{exampleblock}{main.cpp} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} import math; import ; int main() { - Ratio r = golden(); // error (Ratio not visible) - auto r = golden(); // ok: (Ratio is reachable) - std::cout << r.num; // ok: members of reachable - // class types become visible + Ratio r = golden(); // error: Ratio not visible + auto r = golden(); // OK: Ratio is reachable + std::cout << r.num; // OK: members of reachable + // types are visible using R = decltype(r); - R r2{1.f, 3.f}; // ok + R r2{1.f, 3.f}; // OK } \end{cppcode*} \end{exampleblock} @@ -135,20 +135,24 @@ \begin{frame}[fragile] \frametitlecpp[20]{Module structure} \begin{exampleblock}{} - \begin{cppcode*}{gobble=2} - module; - #include // global module fragment - // ... // only preprocessor - // statements allowed - - export module math; // exported module name - - import ; // module preamble - // ... // only imports allowed - - export struct S { ... }; // module purview - export S f() { ... } // starts at first - std::string h() { ... } // non-import + \small + \begin{cppcode*}{} + module; // global module fragment (opt.) + #include // only preprocessor + // ... // statements allowed + + export module math; // module preamble starts with + // exported module name. + import ; // only imports allowed + // ... // + + export struct S { ... }; // module purview started + export S f(); // at first non-import + std::string h() { ... } + + module :private // private module fragment (opt.) + S f() { ... } // changes here trigger + // no recompilation of BMI \end{cppcode*} \end{exampleblock} \end{frame} @@ -156,7 +160,7 @@ \begin{frame}[fragile] \frametitlecpp[20]{Module structure} \begin{alertblock}{Pitfall} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} export module math; #include import ; @@ -170,7 +174,7 @@ \end{itemize} \end{block} \begin{exampleblock}{Put includes into global module fragment} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} module; #include export module math; @@ -186,7 +190,7 @@ Like with header files, we can split interface and implementation: \end{block} \begin{exampleblock}{Interface} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} export module math; export struct S { ... }; @@ -194,7 +198,7 @@ \end{cppcode*} \end{exampleblock} \begin{exampleblock}{Implementation} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} module; #include module math; @@ -206,16 +210,70 @@ \end{exampleblock} \end{frame} +\begin{frame}[fragile,shrink=5] + \frametitlecpp[20]{Submodules and re-exporting} + \begin{block}{} + Modules can (re-)export other modules using \cppinline{export import}: + \end{block} + \vspace{-5mm} + \begin{columns}[t] + \begin{column}{.45\textwidth} + \begin{exampleblock}{Module one} + \begin{cppcode*}{} + export module one; + + export struct S { ... }; + \end{cppcode*} + \end{exampleblock} + \end{column} + \begin{column}{.45\textwidth} + \begin{exampleblock}{Module two} + \begin{cppcode*}{} + export module two; + import ; + + export std::string b() + { ... } + \end{cppcode*} + \end{exampleblock} + \end{column} + \end{columns} + \begin{columns}[t] + \begin{column}{.45\textwidth} + \begin{exampleblock}{Module three} + \begin{cppcode*}{} + export module three; + import one; + export import two; + + export S f() { ... } + \end{cppcode*} + \end{exampleblock} + \end{column} + \begin{column}{.45\textwidth} + \begin{exampleblock}{Module four} + \begin{cppcode*}{} + export module four; + import three; + // b and f visible + // S and std::string only + // reachable + \end{cppcode*} + \end{exampleblock} + \end{column} + \end{columns} +\end{frame} + \begin{frame}[fragile,shrink=5] \frametitlecpp[20]{Module partitions} \begin{block}{} - We can split a module's interface into multiple files: + We can split a module's \emph{interface} into multiple files: \end{block} \vspace{-5mm} \begin{columns}[t] \begin{column}{.4\textwidth} \begin{exampleblock}{Primary module interface} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} export module math; export import :structs; @@ -225,7 +283,7 @@ \end{column} \begin{column}{.5\textwidth} \begin{exampleblock}{Module interface partition} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} export module math:structs; @@ -235,7 +293,7 @@ \end{column} \end{columns} \begin{exampleblock}{Implementation} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} module math; import ; @@ -248,10 +306,10 @@ \begin{frame}[fragile,shrink=5] \frametitlecpp[20]{Module implementation units} \begin{block}{} - We can split a module's implementation into multiple files: + We can split a module's \emph{implementation} into multiple files: \end{block} \begin{exampleblock}{Interface} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} export module math; import :foo; // no export import :bar; // no export @@ -266,7 +324,7 @@ \begin{columns}[t] \begin{column}{.45\textwidth} \begin{exampleblock}{Implementation unit foo} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} module math:foo; S f() { ... } @@ -275,7 +333,7 @@ \end{column} \begin{column}{.45\textwidth} \begin{exampleblock}{Implementation unit bar} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} module math:bar; std::string b() { ... } @@ -285,57 +343,6 @@ \end{columns} \end{frame} -\begin{frame}[fragile,shrink=5] - \frametitlecpp[20]{Submodules and re-exporting} - \begin{block}{} - Modules can (re-)export other modules: - \end{block} - \vspace{-5mm} - \begin{columns}[t] - \begin{column}{.45\textwidth} - \begin{exampleblock}{Module base} - \begin{cppcode*}{gobble=6} - export module base; - - export struct S { ... }; - \end{cppcode*} - \end{exampleblock} - \end{column} - \begin{column}{.45\textwidth} - \begin{exampleblock}{Module bar} - \begin{cppcode*}{gobble=6} - export module bar; - import ; - - export std::string b() - { ... } - \end{cppcode*} - \end{exampleblock} - \end{column} - \end{columns} - \begin{columns}[t] - \begin{column}{.45\textwidth} - \begin{exampleblock}{Module foo} - \begin{cppcode*}{gobble=6} - export module foo; - import export module base; - import bar; - - export S f() { ... } - \end{cppcode*} - \end{exampleblock} - \end{column} - \begin{column}{.45\textwidth} - \begin{exampleblock}{Module math} - \begin{cppcode*}{gobble=6} - export module math; - export import foo; - \end{cppcode*} - \end{exampleblock} - \end{column} - \end{columns} -\end{frame} - \begin{frame}[fragile] \frametitlecpp[23]{Standard library modules} % see: Minimal module support for the standard library http://wg21.link/P2412 @@ -343,11 +350,12 @@ \begin{itemize} \item Use \cppinline|import std;| for the entire C++ standard library. Basically everything in namespace \cppinline|std|. \begin{itemize} - \item E.g.\ \cppinline{memcpy(a, b, n);}, \cppinline{sqrt(val);}, \cppinline{uint32_t i;} will not compile. You need to prefix \cppinline{std::}. + \item E.g.\ \cppinline{memcpy(a, b, n)}, \cppinline{sqrt(val)}, \cppinline{uint32_t} + \item Note that you need to prefix with \cppinline{std::} \end{itemize} \item Use \cppinline|import std.compat;| for the entire C and C++ standard library \item No macros are exported. For these, you still need to include the corresponding headers. - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} import std; // everything C++ #include // for assert() #include // for e.g. CHAR_BIT @@ -419,26 +427,26 @@ \begin{columns} \begin{column}{.35\textwidth} \begin{block}{h.hpp} - \begin{cppcode*}{gobble=6,linenos=false} + \begin{cppcode*}{linenos=false} #include ... \end{cppcode*} \end{block} \begin{block}{a.cpp} - \begin{cppcode*}{gobble=6,linenos=false} + \begin{cppcode*}{linenos=false} #include "h.hpp" ... \end{cppcode*} \end{block} \begin{block}{b.cpp} - \begin{cppcode*}{gobble=6,linenos=false} + \begin{cppcode*}{linenos=false} #include #include "h.hpp" ... \end{cppcode*} \end{block} \begin{block}{c.cpp} - \begin{cppcode*}{gobble=6,linenos=false} + \begin{cppcode*}{linenos=false} #include ... \end{cppcode*} @@ -490,27 +498,27 @@ \begin{column}{.35\textwidth} \small \begin{block}{h.cpp} - \begin{cppcode*}{gobble=6,linenos=false} + \begin{cppcode*}{linenos=false} export module foo; import ; ... \end{cppcode*} \end{block} \begin{block}{a.cpp} - \begin{cppcode*}{gobble=6,linenos=false} + \begin{cppcode*}{linenos=false} import foo; ... \end{cppcode*} \end{block} \begin{block}{b.cpp} - \begin{cppcode*}{gobble=6,linenos=false} + \begin{cppcode*}{linenos=false} import ; import foo; ... \end{cppcode*} \end{block} \begin{block}{c.cpp} - \begin{cppcode*}{gobble=6,linenos=false} + \begin{cppcode*}{linenos=false} #include ... \end{cppcode*} @@ -525,7 +533,7 @@ very thick ] \draw [-Latex](7,10) -- (7,0) node [midway,right] {t}; - \draw[thick] node at(3, 10) [fill=white,rounded rectangle] {build dependencies}; + \draw[thick] node at(3, 10) [fill=white,rounded rectangle] {scan dependencies}; \path[thick] node (vec) at(5,9) [rectangle,draw] {\textless{}vector\textgreater{}} node (vecbmi) at(4,7.5) [rectangle,draw] {vector.bmi} @@ -572,16 +580,22 @@ \item Translation units no longer independent \begin{itemize} \item Extra tool needs to infer dependency graph before building - \item This graph needs to be communicated to the build system - \item We will likely need a \href{https://wg21.link/P1689}{standard dependency file format} + \begin{itemize} + \item Called a ``dependency scanner'' + \item Needs to be communicated to the build system + \item Using a new \href{https://wg21.link/P1689}{standard dependency file format} + \end{itemize} \item Parallel and distributed builds need synchronization \end{itemize} - \item Compilation of module translation units produces binary module interface (BMI) and object file + \item Compilation of module translation units produces a binary module interface (BMI) \emph{and} an object file \begin{itemize} - \item BMIs need to be maintained/shared between multiple compiler instances + \item Need to manage BMIs between multiple compiler invocations \end{itemize} - \item Tools beside the compiler need to build/read binary module interface (static analysis, auto completion, etc.) - \item The C++ standard specifies very little on how this works + \item Tools beside the compiler need to build/read BMIs + \begin{itemize} + \item E.g.\ static analysis, language servers for auto completion, etc. + \end{itemize} + \item The \cpp standard specifies very little on how this works \begin{itemize} \item We may experience large implementation divergence \end{itemize} @@ -593,12 +607,16 @@ \frametitlecpp[20]{Build system} \begin{block}{Status} \begin{itemize} - \item build2 supports everything g++ does - \item MSBuild figures out dependencies and build order automatically (Windows/Visual Studio only) + \item Dependency scanner since clang 16: \texttt{clang-scan-deps} + \item Experimental module support in cmake since 3.26, uses \texttt{clang-scan-deps}, many fixes in later versions % see: https://discourse.cmake.org/t/c-20-modules-update/7330/24 and https://youtu.be/c563KgO-uf4?si=HUpnSivtDxvBoTr3&t=3592 + \item MSBuild (Windows) fully handles dependency scanning \item \cppinline{g++ a.cpp b.cpp ...} ``just works'', must be one line though - \item GCC uses a \href{https://wg21.link/P1184}{module mapper} to resolve imports, which specifies a protocol and uses a central server for module maintenance \item \href{https://wg21.link/p1602}{Experimental extensions to GNU make} exist to grow dependency graph on the fly while modules are discovered - \item No support in cmake (3.23) yet + \item Header units unsupported in all build systems (May 2023) % See talk: https://youtu.be/_LGR0U5Opdg?si=yQoCYD2yGFhi_vs6&t=3768 + \item C++23: \cppinline{import std;} supported in MSVC, partially in clang + \begin{itemize} + \item GCC/clang/MSVC also plan support in C++20 % see: https://github.com/microsoft/STL/issues/3945 + \end{itemize} \end{itemize} \end{block} \end{frame} @@ -607,7 +625,9 @@ \frametitlecpp[20]{Building modules (g++)} \begin{block}{Case study: g++} \begin{itemize} - \item By default, g++ caches compiled module interfaces (CMI, i.e.\ BMI) in subdirectory \texttt{./gcm.cache} + \item BMI is called CMI (compiled module interfaces) + \item By default, g++ caches CMIs in subdirectory \texttt{./gcm.cache} + \item Uses a \href{https://wg21.link/P1184}{module mapper} to resolve imports, which specifies a protocol and uses a central server for module maintenance \item Each module needs to be built before it can be imported \begin{itemize} \item {\footnotesize \cppinline{g++ -std=c++20 -fmodules-ts -c ratio.cpp -o ratio.o}} @@ -630,16 +650,17 @@ \begin{itemize} \item Start writing importable headers (no macro dependencies) \begin{itemize} - \item This allows dual use: \cppinline{#include} before \cpp20 and \cppinline{import} with \cpp20 + \item Dual use: \cppinline{#include} before \cpp20 and \cppinline{import} with \cpp20 \end{itemize} \item Watch progress on module support in your build system \end{itemize} \end{exampleblock} \begin{exampleblock}{Guidance for tomorrow} \begin{itemize} - \item Start importing headers when \cpp20 is available - \item Start writing modules when all users of your code have \cpp20 - \item Start using \cppinline{import std;} when \cpp23 is available + \item Start writing modules when your users have \cpp20 + \item Maybe import standard headers when \cpp20 is available + \item Start using \cppinline{import std;} when available + \item Future of header units unclear (implementation difficulties) \end{itemize} \end{exampleblock} \end{frame} @@ -647,10 +668,12 @@ \begin{frame}[fragile] \frametitlecpp[20]{Modules} \begin{block}{Resources} + \small \begin{itemize} - \item \href{https://www.youtube.com/watch?v=szHV6RdQdg8}{Practical C++ Modules - Boris Kolpackov - CppCon 2019} \item \href{https://www.youtube.com/watch?v=nP8QcvPpGeM}{A (Short) Tour of C++ Modules - Daniela Engert - CppCon 2021} (very technical) \item Understanding C++ Modules: \href{https://vector-of-bool.github.io/2019/03/10/modules-1.html}{Part1}, \href{https://vector-of-bool.github.io/2019/03/31/modules-2.html}{Part2} and \href{https://vector-of-bool.github.io/2019/10/07/modules-3.html}{Part3}. + \item \href{https://www.youtube.com/watch?v=DJTEUFRslbI}{So, You Want to Use C++ Modules … Cross-Platform? - Daniela Engert - C++ on Sea 2023} + \item \href{https://www.youtube.com/watch?v=c563KgO-uf4&t=686s}{import CMake: 2023 State of C++20 modules in CMake - Bill Hoffman - CppNow 2023} \end{itemize} \end{block} \end{frame} @@ -659,13 +682,13 @@ \frametitlecpp[20]{Modules} \begin{exercise}{Modules} \begin{itemize} - \item go to \texttt{code/modules} + \item go to \texttt{exercises/modules} \item convert the \texttt{Complex.hpp} header into a module named \texttt{math} \end{itemize} \end{exercise} \begin{exercise}{Header units} \begin{itemize} - \item go to \texttt{code/header\_units} + \item go to \texttt{exercises/header\_units} \item convert all \cppinline{#include}s into header unit \cppinline{import}s \end{itemize} \end{exercise} diff --git a/talk/expert/perfectforwarding.tex b/talk/expert/perfectforwarding.tex index a28908b7..3cf1db3d 100644 --- a/talk/expert/perfectforwarding.tex +++ b/talk/expert/perfectforwarding.tex @@ -180,7 +180,7 @@ \begin{frame}[fragile] \frametitlecpp[20]{Forwarding references} \begin{alertblock}{Careful!} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template struct S { S(T&& t) { ... } // rvalue references void f(T&& t) { ... } // NOT forwarding references @@ -191,7 +191,7 @@ \begin{overprint} \onslide<1> \begin{exampleblock}{Half-way correct version} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template struct S { template @@ -204,7 +204,7 @@ \end{exampleblock} \onslide<2> \begin{exampleblock}{Correct version} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template struct S { template , T>, int> = 0> diff --git a/talk/expert/sfinae.tex b/talk/expert/sfinae.tex index 7b3d66d5..dc637dfc 100644 --- a/talk/expert/sfinae.tex +++ b/talk/expert/sfinae.tex @@ -51,9 +51,9 @@ \frametitlecpp[11]{\texttt{declval}} \begin{block}{The main idea} \begin{itemize} - \item gives you a reference to a ``fake'' object at compile time + \item function to create reference to a ``fake'' object at compile time \item useful for types that cannot easily be constructed - \item use only in unevaluated contexts, e.g. inside \cppinline{decltype} + \item use only in unevaluated contexts, e.g.\ inside \cppinline{decltype} \end{itemize} \end{block} \begin{exampleblock}{} @@ -159,7 +159,7 @@ \end{itemize} \end{block} \begin{block}{Implementation in header type\_traits} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template using void_t = void; \end{cppcode*} @@ -284,7 +284,7 @@ \begin{frame}[fragile] \frametitlecpp[14]{Back to variadic class templates} \begin{block}{The tuple get function} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template std::enable_if_t>::type&> @@ -304,7 +304,7 @@ \begin{frame}[fragile] \frametitlecpp[17]{with if constexpr and return type deduction} \begin{block}{The tuple get function} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template auto& get(tuple& t) { if constexpr(I == 0) diff --git a/talk/expert/variadictemplate.tex b/talk/expert/variadictemplate.tex index 1a5da4e5..be012f95 100644 --- a/talk/expert/variadictemplate.tex +++ b/talk/expert/variadictemplate.tex @@ -210,7 +210,7 @@ \frametitlecpp[11]{Variadic templates} \begin{exerciseWithShortcut}{Variadic templates}{Variadic tpl} \begin{itemize} - \item go to code/variadic + \item go to \texttt{exercises/variadic} \item you will extend the \cppinline{tuple} from the last slides \item follow the instructions in the source code \end{itemize} diff --git a/talk/introduction/goals.tex b/talk/introduction/goals.tex index b9e461ea..d0ef26d3 100644 --- a/talk/introduction/goals.tex +++ b/talk/introduction/goals.tex @@ -1,7 +1,7 @@ \subsection[Use]{Why we use it?} \begin{frame} - \frametitle{Why is \cpp our language of choice?} + \frametitle{Why is \cpp{} our language of choice?} \begin{block}{Adapted to large projects} \begin{itemize} \item statically and strongly typed @@ -13,7 +13,10 @@ \pause \begin{block}{Fast} \begin{itemize} - \item compiled (unlike Java, C\#, Python, ...) + \item compiled to native machine code + \begin{itemize} + \item unlike Java, C\#, Python, ... + \end{itemize} \item allows to go close to hardware when needed \end{itemize} \end{block} diff --git a/talk/introduction/history.tex b/talk/introduction/history.tex index 3b7b92fc..fcb02969 100644 --- a/talk/introduction/history.tex +++ b/talk/introduction/history.tex @@ -1,7 +1,7 @@ \subsection[Hist]{History} \begin{frame} - \frametitle{C/\cpp origins} + \frametitle{C/\cpp{} origins} \begin{minipage}{0.4\linewidth} \tikzstyle{old}=[ellipse,draw=black,fill=orange!30,thick,inner sep=2pt] \tikzstyle{new}=[rectangle,draw=black,fill=green!50,thick,inner sep=2pt] @@ -85,12 +85,12 @@ \begin{frame} \frametitle{\cpp11, \cpp14, \cpp17, \cpp20, \cpp23, \cpp26...} - \begin{block}{status} + \begin{block}{Status} \begin{itemize} \item A new \cpp specification every 3 years \begin{itemize} - \item \cpp23 complete since 11th of Feb. 2023, awaiting ISO ballot - \item work on \cpp26 has begun + \item \cpp23 complete since 11\textsuperscript{th} of Feb. 2023 + \item work on \cpp26 is ongoing \end{itemize} \item Bringing each time a lot of goodies \end{itemize} @@ -100,8 +100,8 @@ \begin{multicols}{2} \begin{itemize} \item Use a compatible compiler - \item add -std=c++xx to compilation flags - \item e.g. -std=c++17 + \item add \texttt{-std=c++xx} to compilation flags + \item e.g.\ \texttt{-std=c++17} \end{itemize} \vfill \columnbreak @@ -113,7 +113,8 @@ 11 & $\geq$4.8 & $\geq$3.3\\ 14 & $\geq$4.9 & $\geq$3.4\\ 17 & $\geq$7.3 & $\geq$5\\ - 20 & $>$11 & $>$12 \\ + 20 & $\geq$11, 14 & $\geq$19 \\ + 23 & $>$15 & $>$20 \\ \end{tabular} \caption{Minimum versions of gcc and clang for a given \cpp version} \end{center} diff --git a/talk/morelanguage/constexpr.tex b/talk/morelanguage/constexpr.tex index 604fb0f0..de68449e 100644 --- a/talk/morelanguage/constexpr.tex +++ b/talk/morelanguage/constexpr.tex @@ -121,6 +121,33 @@ \end{cppcode*} \end{frame} +\begin{frame}[fragile] + \frametitlecpp[17]{compile-time branches} + \begin{block}{{\it if constexpr}} + \begin{itemize} + \item takes a \cppinline{constexpr} expression as condition + \item evaluates at compile time + \item key benefit: the discarded branch can contain invalid code + \end{itemize} + \end{block} + \begin{exampleblock}{Example code} + \small + \begin{cppcode*}{} + template + auto remove_ptr(T t) { + if constexpr (std::is_pointer_v) { + return *t; + } else { + return t; + } + } + int i = ...; int *j = ...; + int r = remove_ptr(i); // equivalent to i + int q = remove_ptr(j); // equivalent to *j + \end{cppcode*} + \end{exampleblock} +\end{frame} + \begin{frame}[fragile] \frametitlecpp[20]{Immediate functions} \begin{block}{Motivation} diff --git a/talk/morelanguage/constness.tex b/talk/morelanguage/constness.tex index eb66f9eb..ede4ade1 100644 --- a/talk/morelanguage/constness.tex +++ b/talk/morelanguage/constness.tex @@ -16,7 +16,7 @@ int const i = 6; const int i = 6; // equivalent - // error : i is constant + // error: i is constant i = 5; auto const j = i; // works with auto @@ -62,30 +62,35 @@ // type of 'this' is 'Example const*' data = 0; // Error: member function is const } + void foo() { // ok, overload + data = 1; // ok, 'this' is 'Example*' + } int data; }; + Example const e1; e1.foo(); // calls const foo + Example e2; e2.foo(); // calls non-const foo \end{cppcode} \end{frame} \begin{frame}[fragile] - \frametitlecpp[98]{Method constness} + \frametitlecpp[98]{Member function constness} \begin{block}{Constness is part of the type} \begin{itemize} \item \cppinline{T const} and \cppinline{T} are different types - \item however: \cppinline{T} is automatically cast to \cppinline{T const} when needed + \item but: \cppinline{T} is automatically converted to \cppinline{T const} when needed \end{itemize} \end{block} \begin{cppcode} - void func(int & a); - void funcConst(int const & a); + void change(int & a); + void read(int const & a); int a = 0; int const b = 0; - func(a); // ok - func(b); // error - funcConst(a); // ok - funcConst(b); // ok + change(a); // ok + change(b); // error + read(a); // ok + read(b); // ok \end{cppcode} \end{frame} @@ -93,7 +98,7 @@ \frametitlecpp[98]{constness} \begin{exercise}{Constness} \begin{itemize} - \item go to code/constness + \item go to \texttt{exercises/constness} \item open constplay.cpp \item try to find out which lines won't compile \item check your guesses by compiling for real diff --git a/talk/morelanguage/copyelision.tex b/talk/morelanguage/copyelision.tex index adf5bd98..e5fd1917 100644 --- a/talk/morelanguage/copyelision.tex +++ b/talk/morelanguage/copyelision.tex @@ -2,7 +2,7 @@ \begin{frame}[fragile] \frametitlecpp[17]{Copy elision} - \begin{block}{Copy elision} + \begin{block}{Where does it take place ?} \small \begin{cppcode*}{} struct Foo { ... }; diff --git a/talk/morelanguage/exceptions.tex b/talk/morelanguage/exceptions.tex index 57f6b8b9..77a08686 100644 --- a/talk/morelanguage/exceptions.tex +++ b/talk/morelanguage/exceptions.tex @@ -21,7 +21,7 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{fontsize=\small,gobble=2} + \begin{cppcode*}{fontsize=\small} try { process_data(f); } catch (const @@ -30,7 +30,7 @@ } \end{cppcode*} \columnbreak - \begin{cppcode*}{fontsize=\small,firstnumber=7,gobble=2} + \begin{cppcode*}{fontsize=\small,firstnumber=7} void process_data(file &f) { ... if (i >= buffer.size()) @@ -172,7 +172,7 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class C { ... }; void f() { C c1; @@ -185,14 +185,14 @@ } \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=11} + \begin{cppcode*}{firstnumber=11} int main() { try { - C c5; + C c4; g(); cout << "done"; // not run } catch(const exception&) { - // c1, c3 and c5 have been + // c1, c3 and c4 have been // destructed } } @@ -214,7 +214,7 @@ \end{itemize} \item \textit{don't} use exceptions to provide alternative/skip return values \begin{itemize} - \item you can use \cppinline{std::optional} or \cppinline{std::variant} + \item you can use \cppinline{std::optional}, \cppinline{std::variant} or \cppinline{std::expected} (\cpp23) \item avoid using the global C-style \cppinline{errno} \end{itemize} \item never throw in destructors @@ -233,7 +233,7 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{fontsize=\scriptsize,gobble=2} + \begin{cppcode*}{fontsize=\scriptsize} try { for (File const &f : files) { try { @@ -248,7 +248,7 @@ } \end{cppcode*} \columnbreak - \begin{cppcode*}{fontsize=\scriptsize,gobble=2} + \begin{cppcode*}{fontsize=\scriptsize} void process_file(File const & file) { ... if (handle = open_file(file)) @@ -278,7 +278,7 @@ \begin{multicols}{2} \begin{minipage}{5cm} \begin{alertblock}{Avoid} - \begin{cppcode*}{fontsize=\scriptsize,gobble=6,linenos=false} + \begin{cppcode*}{fontsize=\scriptsize,linenos=false} for (string const &num: nums) { try { int i = convert(num); // can @@ -294,7 +294,7 @@ \columnbreak \begin{minipage}{5cm} \begin{exampleblock}{Prefer} - \begin{cppcode*}{fontsize=\scriptsize,gobble=6,linenos=false} + \begin{cppcode*}{fontsize=\scriptsize,linenos=false} for (string const &num: nums) { optional i = convert(num); if (i) { @@ -314,16 +314,16 @@ \begin{block}{\texttt{noexcept}} \begin{itemize} \item a function with the \cppinline{noexcept} specifier states that it guarantees to not throw an exception - \begin{cppcode*}{gobble=2,linenos=false} + \begin{cppcode*}{linenos=false} int f() noexcept; \end{cppcode*} \begin{itemize} \item either no exceptions is thrown or they are handled internally - \item checked at compile time + \item if one is thrown, `std::terminate` is called \item allows the compiler to optimize around that knowledge \end{itemize} \item a function with \cppinline{noexcept(expression)} is only \cppinline{noexcept} when \cppinline{expression} evaluates to \cppinline{true} at compile-time - \begin{cppcode*}{gobble=2,linenos=false} + \begin{cppcode*}{linenos=false} int safe_if_8B() noexcept(sizeof(long)==8); \end{cppcode*} \end{itemize} @@ -346,13 +346,13 @@ \end{itemize} \end{block} \begin{block}{} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} constexpr bool callCannotThrow = noexcept(f()); if constexpr (callCannotThrow) { ... } \end{cppcode*} \end{block} \begin{block}{} - \begin{cppcode*}{gobble=2,firstnumber=3} + \begin{cppcode*}{firstnumber=3} template void g(Function f) noexcept(noexcept(f())) { ... @@ -376,7 +376,7 @@ \item An exception will leave the program in a defined state \begin{itemize} \item At least destructors must be able to run - \item This is similar to the moved from state + \item This is similar to the moved-from state \end{itemize} \item No resources are leaked \end{itemize} diff --git a/talk/morelanguage/initialization.tex b/talk/morelanguage/initialization.tex index aa683f0a..f04c9962 100644 --- a/talk/morelanguage/initialization.tex +++ b/talk/morelanguage/initialization.tex @@ -160,7 +160,7 @@ \item Unless \cppinline{T} has a \cppinline{std::initializer_list} constructor that you do not want to call! \item Be wary of \cppinline{std::vector}! \end{itemize} - \item The STL value initializes when creating new user objects + \item The STL uses value initialisation when resizing containers \begin{itemize} \item E.g. \cppinline{vec.resize(vec.size() + 10);} \end{itemize} diff --git a/talk/morelanguage/lambda.tex b/talk/morelanguage/lambda.tex index 5376b8b6..7c064e4e 100644 --- a/talk/morelanguage/lambda.tex +++ b/talk/morelanguage/lambda.tex @@ -1,36 +1,9 @@ \subsection[$\lambda$]{Lambdas} -\begin{frame}[fragile] - \frametitlecpp[11]{Trailing function return type} - \begin{block}{An alternate way to specify a function's return type} - \begin{cppcode*}{linenos=false} - ReturnType func(Arg1 a, Arg2 b); // classic - auto func(Arg1 a, Arg2 b) -> ReturnType; - \end{cppcode*} - \end{block} - \pause - \begin{block}{Advantages} - \begin{itemize} - \item Allows to simplify inner type definition - \begin{cppcode*}{gobble=4} - class Class { - using ReturnType = int; - ReturnType func(); - } - Class::ReturnType Class::func() {...} - auto Class::func() -> ReturnType {...} - \end{cppcode*} - \item \cpp14: \cppinline{ReturnType} not required, compiler can deduce it - \item used by lambda expressions - \end{itemize} - \end{block} -\end{frame} - - \begin{frame}[fragile] \frametitlecpp[11]{Lambda expressions} \begin{block}{Definition} - a lambda expression is a function with no name + A lambda expression is a function with no name \end{block} \pause \begin{exampleblock}{Python example} @@ -62,7 +35,7 @@ \end{itemize} \end{block} \begin{exampleblock}{Usage example} - \begin{cppcode*}{firstnumber=4,gobble=2} + \begin{cppcode*}{firstnumber=4} int data[]{1,2,3,4,5}; auto f = [](int i) { std::cout << i << " squared is " << i*i << '\n'; @@ -72,6 +45,31 @@ \end{exampleblock} \end{frame} +\begin{frame}[fragile] + \frametitlecpp[11]{Trailing function return type} + \begin{block}{An alternate way to specify a function's return type} + \begin{cppcode*}{linenos=false} + int f(float a); // classic + auto f(float a) -> int; // trailing + auto f(float a) { return 42; } // deduced, C++14 + \end{cppcode*} + \end{block} + \pause + \begin{block}{When to use trailing return type} + \begin{itemize} + \item Only way to specify return type for lambdas + \item Allows to simplify inner type definition + \begin{cppcode*}{} + class Equation { + using ResultType = double; + ResultType evaluate(); + } + Equation::ResultType Equation::evaluate() {...} + auto Equation::evaluate() -> ResultType {...} + \end{cppcode*} + \end{itemize} + \end{block} +\end{frame} \begin{frame}[fragile] \frametitlecpp[11]{Capturing variables} @@ -92,7 +90,7 @@ \end{block} \pause \begin{alertblock}{Error} - \begin{minted}[gobble=6]{text} + \begin{minted}{text} error: 'increment' is not captured [](int x) { return x+increment; }); ^ @@ -134,14 +132,17 @@ \end{exampleblock} \pause \begin{alertblock}{Error} - \begin{minted}[gobble=4]{text} + \begin{minted}{text} error: assignment of read-only variable 'sum' [sum](int x) { sum += x; }); \end{minted} \end{alertblock} \pause \begin{block}{Explanation} - By default, variables are captured by value, and the lambda's \cppinline{operator()} is \cppinline{const}. + \begin{itemize} + \item By default, variables are captured by value + \item The lambda's \cppinline{operator()} is \cppinline{const inline} + \end{itemize} \end{block} \end{frame} @@ -160,75 +161,54 @@ \begin{exampleblock}{Mixed case} One can of course mix values and references \begin{cppcode*}{firstnumber=5} - int sum = 0, offset = 1; + int sum = 0, off = 1; int data[]{1,9,3,8,3,7,4,6,5}; - auto f = [&sum, offset](int x) { sum += x+offset; }; + auto f = [&sum, off](int x) { sum += x + off; }; for (int i : data) f(i); \end{cppcode*} \end{exampleblock} \end{frame} \begin{frame}[fragile] - \frametitlecpp[11]{Capture list} - \begin{block}{all by value} - \begin{cppcode*}{linenos=false} - [=](...) { ... }; - \end{cppcode*} - \end{block} - \pause - \begin{block}{all by reference} - \begin{cppcode*}{linenos=false} - [&](...) { ... }; - \end{cppcode*} - \end{block} - \pause - \begin{block}{mix} - \begin{cppcode*}{linenos=false} - [&, b](...) { ... }; - [=, &b](...) { ... }; + \frametitlecpp[11]{Capture by value vs.\ by reference} + \begin{exampleblock}{See the difference between val and ref} + \begin{cppcode*}{} + int data[]{1,9,3,8,3,7,4,6,5}; + int increment = 3; + auto val = [ inc](int x) { return x+inc; }; + auto ref = [&inc](int x) { return x+inc; }; + + increment = 4; + + for(int& i : data) i = val(i); // increments by 3 + for(int& i : data) i = ref(i); // increments by 4 \end{cppcode*} - \end{block} + \end{exampleblock} \end{frame} -\begin{advanced} - \begin{frame}[fragile] - \frametitlecpp[11]{Capture list - this} - \begin{block}{} - Inside a (non-static) member function, we can capture \cppinline{this}. - \end{block} - \begin{block}{} - \begin{cppcode*}{gobble=2} - [ this](...) { use(*this); }; - [ &](...) { use(*this); }; - [&, this](...) { use(*this); }; - [ =](...) { use(*this); }; // deprecated in C++20 - [=, this](...) { use(*this); }; // allowed in C++20 - \end{cppcode*} - Since the captured \cppinline{this} is a pointer, \cppinline{*this} refers to the object by reference. - \end{block} - \pause - \begin{block}{} - \begin{cppcode*}{gobble=2} - [ *this](...) { use(*this); }; // C++17 - [&, *this](...) { use(*this); }; // C++17 - [=, *this](...) { use(*this); }; // C++17 + \frametitlecpp[14]{Init capture} + \begin{exampleblock}{Capture with an initializer} + In \cpp14, can declare captures with initializers + \begin{cppcode*}{} + auto f = [inc = 1+2](int x) { return x+inc; }; + auto g = [inc = getInc()](int x) { return x+inc; }; + for(int& i : data) i = f(i); // increments by 3 + for(int& i : data) i = g(i); // unknown increment \end{cppcode*} - The object at \cppinline{*this} is captured by value (the lambda gets a copy). - \end{block} - Details in \href{https://www.nextptr.com/tutorial/ta1430524603/capture-this-in-lambda-expression-timeline-of-change}{this blog post}. + \end{exampleblock} \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{Anatomy of a lambda} \begin{block}{Lambdas are pure syntactic sugar - \cppinsightLink{https://cppinsights.io/s/67800da8}} \begin{itemize} - \item they are replaced by a functor during compilation + \item They are replaced by a functor during compilation \end{itemize} \begin{columns} \scriptsize \begin{column}{.25\textwidth} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} int sum = 0, off = 1; auto l = [&sum, off] @@ -244,14 +224,14 @@ \end{cppcode*} \end{column} \begin{column}{.45\textwidth} - \begin{cppcode*}{gobble=6, firstnumber=13} + \begin{cppcode*}{firstnumber=13} int sum = 0, off = 1; struct __lambda4 { - int& sum; - int off; + int& sum; int off; __lambda4(int& s, int o) : sum(s), off(o) {} - auto operator()(int x)const{ + + auto operator()(int x) const { sum += x + off; } }; @@ -261,24 +241,75 @@ \end{column} \end{columns} \end{block} - \begin{exampleblock}{Some nice consequence} + \begin{exampleblock}{Some nice consequences} \begin{itemize} - \item lambda expressions create ordinary objects - \item they can in particular be inherited from! + \item Lambda expressions create ordinary objects + \item They can be copied, moved, or inherited from \end{itemize} \end{exampleblock} \end{frame} +\begin{frame}[fragile] + \frametitlecpp[11]{Capture list} + \begin{block}{all by value} + \begin{cppcode*}{linenos=false} + [=](...) { ... }; + \end{cppcode*} + \end{block} + \pause + \begin{block}{all by reference} + \begin{cppcode*}{linenos=false} + [&](...) { ... }; + \end{cppcode*} + \end{block} + \pause + \begin{block}{mix} + \begin{cppcode*}{linenos=false} + [&, b](...) { ... }; + [=, &b](...) { ... }; + \end{cppcode*} + \end{block} +\end{frame} + +\begin{advanced} + +\begin{frame}[fragile] + \frametitlecpp[11]{Capture list - this} + \begin{block}{} + Inside a (non-static) member function, we can capture \cppinline{this}. + \end{block} + \begin{block}{} + \begin{cppcode*}{} + [ this](...) { use(*this); }; + [ &](...) { use(*this); }; + [&, this](...) { use(*this); }; + [ =](...) { use(*this); }; // deprecated in C++20 + [=, this](...) { use(*this); }; // allowed in C++20 + \end{cppcode*} + Since the captured \cppinline{this} is a pointer, \cppinline{*this} refers to the object by reference. + \end{block} + \pause + \begin{block}{} + \begin{cppcode*}{} + [ *this](...) { use(*this); }; // C++17 + [&, *this](...) { use(*this); }; // C++17 + [=, *this](...) { use(*this); }; // C++17 + \end{cppcode*} + The object at \cppinline{*this} is captured by value (the lambda gets a copy). + \end{block} + Details in \href{https://www.nextptr.com/tutorial/ta1430524603/capture-this-in-lambda-expression-timeline-of-change}{this blog post}. +\end{frame} + \begin{frame}[fragile] \frametitlecpp[14]{Generic lambdas} \begin{block}{Generic lambdas (aka.\ polymorphic lambdas)} \begin{itemize} \item The type of lambda parameters may be \cppinline{auto}. - \begin{cppcode*}{linenos=false,gobble=4} + \begin{cppcode*}{linenos=false} auto add = [](auto a, auto b) { return a + b; }; \end{cppcode*} \item The generated \cppinline{operator()} becomes a template function: - \begin{cppcode*}{linenos=false,gobble=4} + \begin{cppcode*}{linenos=false} template auto operator()(T a, U b) const { return a + b; } \end{cppcode*} @@ -286,11 +317,11 @@ \end{itemize} \end{block} \begin{block}{Explicit template parameters (\cpp20)} + \begin{cppcode*}{linenos=false} + auto add = [](T a, T b) + { return a + b; }; + \end{cppcode*} \begin{itemize} - \begin{cppcode*}{linenos=false,gobble=4} - auto add = [](T a, T b) - { return a + b; }; - \end{cppcode*} \item The types of \cppinline{a} and \cppinline{b} must be the same. \end{itemize} \end{block} @@ -328,21 +359,17 @@ \item Without captures, are default-constructible and assignable \end{itemize} \end{block} - \begin{exampleblock}{Examples} + \begin{exampleblockGB}{Example}{https://godbolt.org/z/GoE6xM8EG}{\texttt{lambda \cpp20}} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} struct S { decltype([](int i) { std::cout << i; }) f; } s; s.f(42); // prints "42" - template - using CudaPtr = std::unique_ptr; - std::set s2; \end{cppcode*} - \end{exampleblock} + \end{exampleblockGB} \end{frame} diff --git a/talk/morelanguage/morestl.tex b/talk/morelanguage/morestl.tex index 0617876d..1c808a8f 100644 --- a/talk/morelanguage/morestl.tex +++ b/talk/morelanguage/morestl.tex @@ -1,4 +1,4 @@ -\subsection[More]{More STL} +\subsection[STD]{Standard library} \begin{frame}[fragile] \frametitlecpp[17]{\texttt{std::string\_view} \cpprefLink{https://en.cppreference.com/w/cpp/string/basic_string_view}} @@ -45,7 +45,7 @@ \begin{itemize} \item and thus to be passed by value \end{itemize} - \item \cppinline{span} can also have a \texttt{static extend} + \item \cppinline{span} can also have a \texttt{static extent} \begin{itemize} \item meaning the size is determined at compile time \item and thus only a pointer is stored by \cppinline{span} @@ -56,10 +56,10 @@ \begin{frame}[fragile] \frametitlecpp[20]{\texttt{std::span} - Usage} - \begin{exampleblockGB}{Some example}{https://godbolt.org/z/W81sjrbxK}{\texttt{span}} + \begin{exampleblockGB}{Some example}{https://godbolt.org/z/nP3jr388z}{\texttt{span}} \scriptsize \begin{cppcode*}{} - void print(std::span c) { + void print(std::span c) { for (auto a : c) { std::cout << a << " "; } std::cout << "\n"; } @@ -70,16 +70,16 @@ print(a); // 23 45 67 89 std::vector v{1, 2, 3, 4, 5}; - std::span sv = v; + std::span sv = v; print(sv.subspan(2, 2)); // 3 4 std::array a2{-14, 55, 24}; times2(a2); print(a2); // -28 110 48 - std::span sa2 = a2; + std::span sa2 = a2; std::cout << sizeof(sv) << " " << sizeof(sa2) << "\n"; // 16 8 - std::span s2a2 = a; // compilation failure, invalid conversion + std::span s2a2 = a; // compilation failure, invalid conversion \end{cppcode*} \end{exampleblockGB} \end{frame} @@ -99,10 +99,11 @@ \begin{cppcode*}{} std::optional parse_phone(std::string_view in) { if (is_valid_phone(in)) - return in; // equiv. to optional{in}; + return in; // equiv. to optional{in}; return {}; // or: return std::nullopt; (empty opt.) } - if (v) { // or: v.is_valid() + auto v = parse_phone(...); + if (v) { // or: v.has_value() process_phone(v.value()); // or: *v (unchecked) v->call(); // calls Phone::call() (unchecked) } @@ -140,13 +141,42 @@ \frametitlecpp[17]{std::optional} \begin{exerciseWithShortcut}{Handling optional results}{std::optional} \begin{itemize} - \item go to \texttt{code/optional/optional.cpp} + \item go to \texttt{exercises/optional/optional.cpp} \item modify \texttt{mysqrt} so to return an \texttt{std::optional} \item guess the consequences on \texttt{square} \end{itemize} \end{exerciseWithShortcut} \end{frame} +\begin{frame}[fragile] + \frametitlecpp[23]{std::expected \cpprefLink{https://en.cppreference.com/w/cpp/utility/expected}} + \begin{block}{Manages either of 2 values (expected or not)} + \begin{itemize} + \item templated by the 2 value types + \item Useful for the return value of a function that may fail + \begin{itemize} + \item and would then return another type (error type) + \end{itemize} + \end{itemize} + \end{block} + \begin{exampleblockGB}{Practically}{https://godbolt.org/z/9rjn1hzeW}{\texttt{expected}} + \small + \begin{cppcode*}{} + enum class Error {...}; + std::expected parse(std::string_view in) { + if (is_valid(in)) return convert_to_int(in); + return std::unexpected(Error::...); + } + auto v = parse(...); + if (v) { // or v.has_value() + std::cout << *v; // (unchecked) + foo(v.value()); // may throw bad_expected_access + } else + log(v.error()); + \end{cppcode*} + \end{exampleblockGB} +\end{frame} + \begin{frame}[fragile] \frametitlecpp[17]{std::variant \cpprefLink{https://en.cppreference.com/w/cpp/utility/variant}} \begin{block}{A type-safe union} @@ -187,7 +217,7 @@ \end{block} \begin{exampleblockGB}{Practically}{https://godbolt.org/z/bxzEsM3de}{\texttt{variant visitor}} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} struct Visitor { auto operator()(int i) {return "i:"+std::to_string(i);} auto operator()(float f) {return "f:"+std::to_string(f);} @@ -212,7 +242,7 @@ \end{block} \begin{exampleblockGB}{Practically}{https://godbolt.org/z/WcdnT1hra}{\texttt{variant $\lambda$ visitor}} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template // covered in expert part struct Overload : Ts ... { using Ts::operator()...; }; template @@ -234,7 +264,7 @@ \frametitlecpp[17]{std::variant exercise} \begin{exerciseWithShortcut}{An alternative to oo polymorphism}{std::variant} \begin{itemize} - \item go to \texttt{code/variant/variant.cpp} + \item go to \texttt{exercises/variant/variant.cpp} \item replace inheritance from a common base class with an \cppinline{std::variant} on the three kinds of particle. \item two solutions given : with \cppinline{std::get_if} and with \cppinline{std::visit}. \end{itemize} @@ -349,7 +379,8 @@ \item CV qualifiers and references allowed \end{itemize} \end{block} - \begin{exampleblock}{} + \begin{exampleblockGB}{Practically}{https://godbolt.org/z/PfWzxjP7W}{\texttt{Structured binding}} + \small \begin{cppcode*}{} std::tuple tuple = ...; auto [ a, b, c ] = tuple; @@ -363,7 +394,7 @@ std::unordered_map map = ...; for (const auto& [key, value] : map) { ... } \end{cppcode*} - \end{exampleblock} + \end{exampleblockGB} \end{frame} \begin{frame}[fragile] @@ -381,7 +412,7 @@ \end{block} \begin{exampleblock}{} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class C { ... }; // complex, cannot change template <> struct std::tuple_size { @@ -396,28 +427,57 @@ \end{frame} \begin{frame}[fragile] - \frametitlecpp[17]{compile-time branches} - \begin{block}{{\it if constexpr}} + \frametitle{\cpp date and time utilities \hfill \cpp11 / \cpp20} + \cppinline{std::chrono library} present in \cppinline{} header + \begin{block}{Clocks} \begin{itemize} - \item takes a \cppinline{constexpr} expression as condition - \item evaluates at compile time - \item key benefit: the discarded branch can contain invalid code + \item consists of a starting point (or epoch) and a tick rate + \begin{itemize} + \item E.g. January 1, 1970 and every second + \end{itemize} + \item C++ defines several clock type + \begin{itemize} + \item \href{https://en.cppreference.com/w/cpp/chrono/system_clock}{\color{blue!50!white} \cppinline{system_clock}} system time, aka wall clock time, or C time + \item \href{https://en.cppreference.com/w/cpp/chrono/steady_clock}{\color{blue!50!white} \cppinline{steady_clock}} monotonic but unrelated to wall clock time + \item \href{https://en.cppreference.com/w/cpp/chrono/high_resolution_clock}{\color{blue!50!white} \cppinline{high_resolution_clock}} clock with the smallest tick period + \end{itemize} \end{itemize} \end{block} - \begin{exampleblock}{Example code} + \begin{block}{\href{https://en.cppreference.com/w/cpp/chrono/time_point}{\color{blue!50!white} \cppinline{time_point}} and \href{https://en.cppreference.com/w/cpp/chrono/duration}{\color{blue!50!white} \cppinline{duration}}} + \begin{itemize} + \item provide easy manipulation of times and duration + \item clock dependent + \item \href{https://en.cppreference.com/w/cpp/chrono/duration/duration_cast}{\color{blue!50!white} \cppinline{duration_cast}} allows conversions between duration types + \begin{itemize} + \item available helper types : nanoseconds, microseconds, milliseconds, seconds, minutes, hours, ... + \end{itemize} + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}[fragile] + \frametitlecpp[11]{Practical usage / timing some \cpp code} + \begin{exampleblockGB}{How to measure the time spent in some code}{https://godbolt.org/z/PzKWer5eb}{\texttt{timing}} \small \begin{cppcode*}{} - template - auto remove_ptr(T t) { - if constexpr (std::is_pointer_v) { - return *t; - } else { - return t; - } - } - int i = ...; int *j = ...; - int r = remove_ptr(i); // equivalent to i - int q = remove_ptr(j); // equivalent to *j + #include + + std::chrono::high_resolution_clock clock; + auto start = clock::now(); + ... // code to be timed + std::chrono::duration ticks = clock.now() - start; + + auto millis = std::chrono::duration_cast + (ticks); + std::cout << "it took " << ticks.count() << " ticks" + << ", that is " << millis.count() << " ms\n"; \end{cppcode*} - \end{exampleblock} + \end{exampleblockGB} + \pause + \begin{alertblock}{Warning} + \begin{itemize} + \item this does not measure the amount of CPU used ! + \item neither the time spent on a CPU (think suspended threads) + \end{itemize} + \end{alertblock} \end{frame} diff --git a/talk/morelanguage/move.tex b/talk/morelanguage/move.tex index 455c6bac..038c7baa 100644 --- a/talk/morelanguage/move.tex +++ b/talk/morelanguage/move.tex @@ -47,7 +47,7 @@ \begin{exampleblock}{Another potentially inefficient code} \begin{overprint} \onslide<1-2> - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} T consumeVector(std::vector input) { // ... change elements, compute result return result; @@ -57,7 +57,7 @@ consumeVector(values); // being copied now \end{cppcode*} \onslide<3-4> - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} T consumeVector(std::vector const & input) { std::vector tmp{input}; // ... change elements, compute result @@ -67,7 +67,7 @@ consumeVector(values); // maybe copied internally? \end{cppcode*} \onslide<5-> - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} T consumeVector(std::vector & input) { // ... change elements, compute result return result; @@ -223,7 +223,7 @@ \frametitlecpp[11]{Move semantics: recommended implementation} \begin{exampleblock}{Practically} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class Movable { Movable(); Movable(const Movable &other); @@ -248,7 +248,7 @@ \frametitlecpp[11]{Move semantics: alternative implementation} \begin{exampleblock}{Practically} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class Movable { Movable(); Movable(const Movable &other); @@ -271,7 +271,7 @@ \frametitlecpp[11]{Move Semantic} \begin{exercise}{Move semantics} \begin{itemize} - \item go to code/move + \item go to \texttt{exercises/move} \item look at the code and run it with callgrind \item understand how inefficient it is \item implement move semantic the easy way in NVector diff --git a/talk/morelanguage/raii.tex b/talk/morelanguage/raii.tex index d8539d29..b895fe07 100644 --- a/talk/morelanguage/raii.tex +++ b/talk/morelanguage/raii.tex @@ -37,8 +37,8 @@ read_line(s); vec.push_back(s); set.add(s); - std::thread t1(func1, vec); - std::thread t2(func2, set); + std::thread t1{func1, vec}; + std::thread t2{func2, set}; \end{cppcode*} \end{exampleblock} \end{frame} @@ -84,7 +84,7 @@ \frametitlecpp[98]{RAII in practice} \begin{exampleblock}{An RAII File class} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class File { public: // constructor: acquire resource @@ -121,9 +121,9 @@ } \end{cppcode*} \end{exampleblock} - \begin{goodpracticeWithShortcut}{Use \texttt{std::fstream} for file handling}{Use \texttt{std::fstream}} + \begin{goodpractice}[Use \texttt{std::fstream}]{Use \texttt{std::fstream} for file handling} The standard library provides \cppinline{std::fstream} to handle files, use it! - \end{goodpracticeWithShortcut} + \end{goodpractice} \end{frame} \begin{frame}[fragile] @@ -248,7 +248,7 @@ void observe(const T&); void modifyRef(T&); void modifyPtr(T*); - void consumer(std::unique_ptr); + void consume(std::unique_ptr); std::unique_ptr pt{produce()}; // Receive ownership observe(*pt); // Keep ownership modifyRef(*pt); // Keep ownership @@ -277,7 +277,7 @@ \begin{frame}[fragile] \frametitlecpp[11]{\texttt{std::shared\_ptr}} - \begin{block}{\mintinline{cpp}{std::shared_ptr} : a reference counting pointer} + \begin{block}{\mintinline{cpp}{std::shared_ptr} : a reference-counting pointer} \begin{itemize} \item wraps a regular pointer similar to \cppinline{unique_ptr} \item has move and copy semantic @@ -335,9 +335,9 @@ \begin{frame}[fragile] \frametitlecpp[11]{Quiz: \texttt{std::shared\_ptr} in use} - \begin{exampleblockGB}{What is the output of this code?}{https://godbolt.org/z/vM35Y6qEW}{\texttt{std::shared\_ptr} quiz} + \begin{exampleblockGB}{What is the output of this code?}{https://godbolt.org/z/vM35Y6qEW}{\texttt{shared\_ptr} quiz} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} auto shared = std::make_shared(100); auto print = [shared](){ std::cout << "Use: " << shared.use_count() << " " @@ -355,7 +355,7 @@ \pause \begin{block}{} \small - \begin{minted}[autogobble=true]{bash} + \begin{minted}{bash} Use: 2 value: 100 Use: 3 value: 101 Use: 2 value: 101 @@ -385,7 +385,7 @@ \end{exampleblock} \begin{block}{} \small - \begin{minted}[autogobble=true]{bash} + \begin{minted}{bash} Use: 1 value: 100 Use: 2 value: 101 Use: 1 value: 101 @@ -397,9 +397,9 @@ \begin{frame}[fragile] \frametitlecpp[11]{Quiz: \texttt{shared\_ptr} and \texttt{weak\_ptr} in use} - \begin{exampleblockGB}{What is the output of this code?}{https://godbolt.org}{\texttt{shared\_ptr} vs.\ \texttt{weak\_ptr} quiz} + \begin{exampleblockGB}{What is the output of this code?}{https://godbolt.org}{\texttt{shared/weak\_ptr}} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} auto shared = std::make_shared(100); std::weak_ptr weak{ shared }; print(); // with print as before @@ -415,7 +415,7 @@ \pause \begin{block}{} \small - \begin{minted}[autogobble=true]{bash} + \begin{minted}{bash} Use: 1 value: 100 Use: 2 value: 101 Use: 1 value: 101 @@ -426,15 +426,38 @@ \end{advanced} +\begin{frame}[fragile] + \frametitlecpp[11]{Rule of zero} + \begin{goodpractice}[Single responsibility principle]{Single responsibility principle (SRP)} + Every class should have only one responsibility. + \end{goodpractice} + \begin{goodpractice}{Rule of zero} + \begin{itemize} + \item If your class has any special member functions (except ctor.) + \begin{itemize} + \item Your class probably deals with a resource, use RAII + \item Your class should only deal with this resource (SRP) + \item Apply rule of 3/5: write/default/delete all special members + \end{itemize} + \item Otherwise: do not declare any special members (rule of zero) + \begin{itemize} + \item A constructor is fine, if you need some setup + \item If your class holds a resource as data member:\\ + wrap it in a smart pointer, container, or any other RAII class + \end{itemize} + \end{itemize} + \end{goodpractice} +\end{frame} + \begin{frame}[fragile] \frametitlecpp[98]{smart pointers} \begin{exercise}{Smart pointers} \begin{itemize} - \item go to code/smartPointers + \item go to \texttt{exercises/smartPointers} \item compile and run the program. It doesn't generate any output. \item Run with valgrind if possible to check for leaks { \scriptsize - \begin{minted}[gobble=6]{shell-session} + \begin{minted}{shell-session} $ valgrind --leak-check=full --track-origins=yes ./smartPointers \end{minted} } diff --git a/talk/morelanguage/random.tex b/talk/morelanguage/random.tex index a97dd669..71fd7787 100644 --- a/talk/morelanguage/random.tex +++ b/talk/morelanguage/random.tex @@ -85,7 +85,7 @@ \end{itemize} \end{block} \begin{exampleblock}{Distributions sharing a non-deterministic engine} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} std::random_device rd; std::default_random_engine engine{rd()}; std::uniform_int_distribution dist(2,7);//min,max @@ -116,7 +116,7 @@ \item E.g.: \cppinline{rand() % 10} yields biased numbers and is wrong! \end{itemize} \end{alertblock} - \begin{goodpracticeWithShortcut}{Strongly avoid the C random library}{C random library} + \begin{goodpractice}[C random library]{Strongly avoid the C random library} Use the \cpp11 facilities from the \cppinline{} header - \end{goodpracticeWithShortcut} + \end{goodpractice} \end{frame} diff --git a/talk/morelanguage/ranges.tex b/talk/morelanguage/ranges.tex index f8e6d71d..8d3672a0 100644 --- a/talk/morelanguage/ranges.tex +++ b/talk/morelanguage/ranges.tex @@ -32,7 +32,7 @@ \end{block} \begin{exampleblockGB}{Example}{https://godbolt.org/z/d1vTv4TMa}{Views} { \small - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} auto const numbers = std::views::iota(0, 6); auto even = [](int i) { return 0 == i % 2; }; auto square = [](int i) { return i * i; }; @@ -72,7 +72,7 @@ \end{itemize} \end{block} \begin{exampleblockGB}{Example}{https://godbolt.org/z/bWe6W69oE}{Lazy view} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} // print first 20 prime numbers above 1000000 for (int i: std::views::iota(1000000) | std::views::filter(odd) diff --git a/talk/morelanguage/stl.tex b/talk/morelanguage/stl.tex index 45d1dad4..ea8a3af4 100644 --- a/talk/morelanguage/stl.tex +++ b/talk/morelanguage/stl.tex @@ -26,20 +26,19 @@ \begin{frame}[fragile] \frametitlecpp[14]{STL in practice} \begin{exampleblockGB}{STL example}{https://godbolt.org/z/n8ahEr5f6}{STL} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} #include #include #include // `import std;` in C++23 #include #include - std::vector in{5, 3, 4}; // initializer list - std::vector out(3); // constructor taking size - std::transform(in.begin(), in.end(), // range1 - in.begin(), // start range2 - out.begin(), // start result - std::multiplies{}); // function obj - std::copy(out.begin(), out.end(), // 25 9 16 + std::vector in{5, 3, 4}; // initializer list + std::vector out(3); // constructor taking size + std::transform(in.begin(), in.end(), // input range + out.begin(), // start result + std::negate{}); // function obj + std::copy(out.begin(), out.end(), // -5 -3 -4 std::ostream_iterator{std::cout, " "}); \end{cppcode*} \end{exampleblockGB} @@ -123,6 +122,55 @@ \end{cppcode*} \end{frame} +\begin{frame}[fragile] + \frametitlecpp[11]{Containers: \texttt{std::unordered\_map}} + \begin{block}{} + Conceptually a container of \cppinline{std::pair} + \end{block} + \begin{cppcode*}{} + #include + std::unordered_map m; + m["hello"] = 1; // inserts new key, def. constr. value + m["hello"] = 2; // finds existing key + auto [it, isNewKey] = m.insert({"hello", 0}); // no effect + // ^ C++17: "Structured binding" + int val = m["world"]; // inserts new key (val == 0) + int val = m.at("monde"); // throws std::out_of_range + + if (auto it = m.find("hello"); it != m.end()) // C++17 + m.erase(it); // remove by iterator (fast) + if (m.contains("hello")) // C++20 + m.erase("hello"); // remove by key, 2. lookup, bad + for (auto const& [k, v] : m) // iterate k/v pairs (C++17) + std::cout << k << ": " << v << '\n'; + \end{cppcode*} +\end{frame} + +\begin{frame}[fragile] + \frametitlecpp[11]{\texttt{std::hash}} + \begin{block}{} + \begin{itemize} + \item The standard utility to create hash codes + \item Used by \cppinline{std::unordered_map} and others + \item Can be customized for your types via template specialization + \end{itemize} + \end{block} + \begin{cppcode*}{} + #include + std::hash h; + std::cout << h("hello"); // 2762169579135187400 + std::cout << h("world"); // 8751027807033337960 + + class MyClass { int a, b; ... }; + template<> struct std::hash { + std::size_t operator()(MyClass const& c) { + std::hash h; + return h(c.a) ^ h(c.b); // xor to combine hashes + } + }; + \end{cppcode*} +\end{frame} + \begin{frame}[fragile] \frametitlecpp[11]{STL's concepts} \begin{block}{iterators} @@ -142,7 +190,7 @@ \end{block} \begin{exampleblockGB}{Iterator example}{https://godbolt.org/z/jv1qTo5xz}{Iterator example} \begin{cppcode*}{} - std::vector const v = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + std::vector const v = {1,2,3,4,5,6,7,8,9}; auto const end = v.rend() - 3; // arithmetic for (auto it = v.rbegin(); it != end; // compare positions @@ -237,12 +285,12 @@ \end{cppcode*} \end{exampleblock} \pause - \begin{goodpracticeWithShortcut}{Use STL algorithms with lambdas}{STL and lambdas} + \begin{goodpractice}[STL and lambdas]{Use STL algorithms with lambdas} \begin{itemize} \item Prefer lambdas over functors when using the STL \item Avoid binders like \cppinline{std::bind2nd}, \cppinline{std::ptr_fun}, etc. \end{itemize} - \end{goodpracticeWithShortcut} + \end{goodpractice} \end{frame} \begin{frame}[fragile] @@ -289,7 +337,7 @@ // Computes sin(x)/(x + DBL_MIN) for elements of a range. std::vector r(l.size()); std::transform(l.begin(), l.end(), r.begin(), - [](auto x) { return sin(x)/(x + DBL_MIN); }); + [](auto x) { return std::sin(x)/(x + DBL_MIN); }); // reduce/fold (using addition) const auto sum = std::reduce(v.begin(), v.end()); @@ -328,7 +376,7 @@ \frametitlecpp[98]{Using the STL} \begin{exercise}{STL} \begin{itemize} - \item go to code/stl + \item go to \texttt{exercises/stl} \item look at the non STL code in randomize.nostl.cpp \begin{itemize} \item it creates a vector of ints at regular intervals diff --git a/talk/morelanguage/templates.tex b/talk/morelanguage/templates.tex index ae96fbd0..81c132a4 100644 --- a/talk/morelanguage/templates.tex +++ b/talk/morelanguage/templates.tex @@ -28,7 +28,7 @@ \begin{frame}[fragile] \frametitlecpp[98]{Templates} - \begin{alertblock}{Warning} + \begin{block}{Notes on templates} \begin{itemize} \item they are compiled for each instantiation \item they need to be defined before used @@ -38,12 +38,12 @@ \end{itemize} \item this may lead to longer compilation times and bigger binaries \end{itemize} - \end{alertblock} + \end{block} \newsavebox{\codepiece} \begin{lrbox}{\codepiece} \begin{minipage}{.35\linewidth} \small - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} template T func(T a) { return a; @@ -55,7 +55,7 @@ \begin{lrbox}{\codepiecea} \begin{minipage}{.4\linewidth} \small - \begin{cppcode*}{gobble=4,linenos=false} + \begin{cppcode*}{linenos=false} int func(int a) { return a; } @@ -66,7 +66,7 @@ \begin{lrbox}{\codepieceb} \begin{minipage}{.4\linewidth} \small - \begin{cppcode*}{gobble=4,linenos=false} + \begin{cppcode*}{linenos=false} double func(double a) { return a; } @@ -96,17 +96,19 @@ struct Map { void set(const KeyType &key, ValueType value); ValueType get(const KeyType &key); + ... }; Map m1; Map m2; // Map Map<> m3; // Map + Map m4; // Map, C++17 \end{cppcode*} \end{frame} \begin{frame}[fragile] \frametitlecpp[98]{Template parameters} - \begin{block}{\texttt{typename} vs. \texttt{class} keyword} + \begin{block}{\texttt{typename} vs.\ \texttt{class} keyword} \begin{itemize} \item for declaring a template type parameter, the \cppinline{typename} and \cppinline{class} keyword are semantically equivalent @@ -151,6 +153,33 @@ \end{cppcode*} \end{frame} +\begin{advanced} + +\begin{frame}[fragile] + \frametitlecpp[98]{Nested templates} + \begin{exampleblockGB}{Nested templates}{https://godbolt.org/z/a9nPnK9jx}{Nested templates} + \small + \begin{cppcode*}{} + template + struct Map { + template + void set(const KeyType &key, OtherValueType value) { + ... + } + template + ValueType get(const OtherKeyType &key); + }; + + template //for class + template //for member function + ValueType Map::get + (const OtherKeyType &key) { ... } + \end{cppcode*} + \end{exampleblockGB} +\end{frame} + +\end{advanced} + \begin{frame}[fragile] \frametitle{Non-type template parameter \hfill \cpp98 / \cpp17 / \cpp20} \begin{block}{template parameters can also be values} @@ -253,7 +282,7 @@ \onslide<1> \begin{alertblock}{} \footnotesize - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template void f(typename C::value_type v) { ... } f(std::vector{...}); // cannot deduce C @@ -271,7 +300,7 @@ \onslide<2> \begin{alertblock}{} \footnotesize - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} template void reduce(const C& cont, const F& f = std::plus{}); reduce(std::vector{...}); // error: cannot deduce F @@ -391,17 +420,19 @@ \item Since \cpp20: CTAD for aggregates (no constructor needed) \end{itemize} \end{block} - \begin{cppcode*}{} - template - struct Triple { - Triple(A a, B b, C c) : a(a), b(b), c(c) {} // C++17 - A a; B b; C c; - }; - - Triple t{42, true, 3.14}; // Triple - Triple t{42, true, 3.14}; // compilation error - Triple t{42, true, 3.14}; // not CTAD - \end{cppcode*} + \begin{exampleblockGB}{Practically}{https://godbolt.org/z/eYhsMcGsW}{CTAD} + \small + \begin{cppcode*}{} + template + struct Triple { + Triple(A a, B b, C c) : a(a), b(b), c(c) {} + A a; B b; C c; + }; + Triple t{42, true, 3.14}; // Triple + Triple t2{42, true, 3.14}; // compilation error + Triple t3{42, true, 3.14}; // not CTAD + \end{cppcode*} + \end{exampleblockGB} \end{frame} \begin{frame}[fragile] @@ -420,14 +451,14 @@ \end{cppcode} \begin{overprint}[\columnwidth] \onslide<1> - \begin{cppcode*}{gobble=2,firstnumber=6} + \begin{cppcode*}{firstnumber=6} Pair p{42, "hello"}; // Pair \end{cppcode*} \onslide<2> - \begin{cppcode*}{gobble=2,firstnumber=6} + \begin{cppcode*}{firstnumber=6} template Pair(A, const char*) -> Pair; @@ -439,7 +470,7 @@ \begin{frame}[fragile] \frametitlecpp[17]{Class Template Argument Deduction (CTAD)} \begin{block}{Standard library examples} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} std::pair p{1.2, true}; // std::pair std::tuple t{1.2, true, 32}; // std::tuple @@ -448,7 +479,7 @@ std::array a{1, 2, 3}; // std::array std::mutex m; - std::lock_guard l(m); // std::lock_guard + std::lock_guard l{m}; // std::lock_guard \end{cppcode*} \end{block} \end{frame} @@ -459,7 +490,7 @@ \frametitlecpp[98]{The full power of templates} \begin{exercise}{Templates} \begin{itemize} - \item go to code/templates + \item go to \texttt{exercises/templates} \item look at the OrderedVector code \item compile and run playwithsort.cpp. See the ordering \item modify playwithsort.cpp and reuse OrderedVector with Complex diff --git a/talk/objectorientation/adl.tex b/talk/objectorientation/adl.tex index 24ab1186..7c12eda0 100644 --- a/talk/objectorientation/adl.tex +++ b/talk/objectorientation/adl.tex @@ -19,7 +19,10 @@ \begin{itemize} \item here for \cppinline{std} and \cppinline{operator<<} \item name is looked for in a sequence of scopes until found - \item remaining scopes are not examined + \begin{itemize} + \item remaining scopes are not examined + \end{itemize} + \item in parallel Argument Dependent Loopkup (ADL) may happen \end{itemize} \end{itemize} \end{block} @@ -32,11 +35,14 @@ \item file (only for global level usage) \item current namespace/block, enclosing namespaces/blocks, etc... \item current class if any, base classes if any, etc... - \item for a call expression (e.g.\ \cppinline{f(a, b)} or \cppinline{a + b}), Argument Dependent Lookup (ADL) \end{itemize} \end{block} \begin{exampleblock}{Argument Dependent Lookup (simplified)} - To find a function name (including operators), the compiler also examines the arguments. For each argument, it searches: + Only for call expression + \begin{itemize} + \item e.g.\ \cppinline{f(a, b)} or \cppinline{a + b} + \end{itemize} + The compiler also examines arguments one by one and searches: \begin{itemize} \item class, if any \item direct and indirect base classes, if any @@ -54,7 +60,7 @@ \begin{column}{.35\textwidth} Don't write : \vspace{-1mm} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} namespace MyNS { struct A { T func(...); @@ -65,7 +71,7 @@ \begin{column}{.35\textwidth} Prefer : \vspace{-1mm} - \begin{cppcode*}{gobble=6,firstnumber=6} + \begin{cppcode*}{firstnumber=6} namespace MyNS { struct A { ... }; T func(const A&, ..); @@ -93,14 +99,14 @@ \begin{columns}[t] \begin{column}{.35\textwidth} Don't write : - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} N::A a,b; std::swap(a, b); \end{cppcode*} \end{column} \begin{column}{.35\textwidth} Prefer : - \begin{cppcode*}{gobble=6,firstnumber=3} + \begin{cppcode*}{firstnumber=3} N::A a,b; using std::swap; swap(a, b); @@ -115,7 +121,7 @@ \end{itemize} \begin{columns} \begin{column}{.7\textwidth} - \begin{cppcode*}{gobble = 6,firstnumber=6} + \begin{cppcode*}{firstnumber=6} namespace N { class A { ... }; // optimized swap for A @@ -140,7 +146,7 @@ \end{block} \begin{exampleblock}{\texttt{operator<<} as hidden friend} \small - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class Complex { float m_real, m_imag; public: @@ -165,20 +171,36 @@ \item Avoid accidental implicit conversions \end{itemize} \end{block} - \begin{alertblock}{Accidental conversions - two examples in one} + \begin{alertblock}{Accidental conversions - two counterexamples} \footnotesize - \begin{cppcode*}{gobble=2} + \begin{overprint} + \onslide<1> + \begin{cppcode*}{} + std::ostream & operator<< // out-of-class definition + (std::ostream & os, Complex const & c) { ... } + struct Fraction { + int num, denom; + operator Complex() const { ... } // conversion op + }; + + std::cout << Fraction{2,4}; // Prints 0.5 + 0i + //calls operator<<(std::cout, Fraction{2,4}.operator Complex()); + \end{cppcode*} + + \onslide<2> + \begin{cppcode*}{} std::ostream & operator<< // out-of-class definition (std::ostream & os, Complex const & c) { ... } struct Fraction { int num, denom; - operator Complex() const { ... } // case 1: conversion op }; - Complex::Complex(Fraction f); // or case 2: converting ctor - std::cout << Fraction{2,4}; // Prints 0.5 + 0i, would call: - //case 1: operator<<(std::cout, Fraction{2,4}.operator Complex()); - //case 2: operator<<(std::cout, Complex(Fraction{2, 3})); + + Complex::Complex(Fraction f); // converting ctor + + std::cout << Fraction{2,4}; // Prints 0.5 + 0i + //calls operator<<(std::cout, Complex(Fraction{2, 3})); \end{cppcode*} + \end{overprint} \end{alertblock} \end{frame} diff --git a/talk/objectorientation/advancedoo.tex b/talk/objectorientation/advancedoo.tex index af7d0b9c..420fcd59 100644 --- a/talk/objectorientation/advancedoo.tex +++ b/talk/objectorientation/advancedoo.tex @@ -1,5 +1,44 @@ \subsection[advOO]{Advanced OO} +\begin{frame}[fragile] + \frametitlecpp[98]{\texttt{this} keyword} + \begin{block}{How to know an object's address?} + \begin{itemize} + \item Sometimes we need the address of the current object + \item Or we need to pass our address / a reference to a different entity + (for example to implement operators, see later) + \item All class methods can use the keyword \cppinline{this} + \begin{itemize} + \item It returns the address of the current object + \item Its type is \cppinline{T*} in the methods of a class {\ttfamily T} + \end{itemize} + \end{itemize} + \end{block} + \begin{minipage}{0.7\textwidth} + \begin{cppcode} + struct S { + int a,b; + // these two are the same: + int getB() { return b; } // 5 + int getB() { return this->b; } // 5 + void testAddress() { + S* addr = this; // 0x3000 + } + } s{2,5}; + \end{cppcode} + \end{minipage}% + \hfil% + \begin{minipage}{0.3\textwidth} + \begin{tikzpicture} + \memorystack[size x=2cm,word size=1,block size=4,nb blocks=4] + \memorypush{a = 2} + \memorypush{b = 5} + \memorystruct{1}{2}{\tiny s} + \draw[-Triangle,thick] (\stacksizex-1*\stacksizey,-1*\stacksizey) node[left] {\footnotesize \cppinline{this} pointer} -- (\stacksizex,-0.1*\stacksizey); + \end{tikzpicture} + \end{minipage} +\end{frame} + \begin{frame}[fragile] \frametitlecpp[98]{Polymorphism} \begin{block}{the concept} @@ -9,7 +48,7 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} Polygon p; int f(Drawable & d) {...} @@ -60,7 +99,7 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} Polygon p; int f(Drawable & d) {...} @@ -93,7 +132,7 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} Polygon p; p.draw(); // ? @@ -129,7 +168,7 @@ \begin{overprint} \onslide<2> \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} Polygon p; p.draw(); // Polygon.draw @@ -153,7 +192,7 @@ \onslide<3> \begin{multicols}{2} - \begin{cppcode*}{gobble=2,highlightlines=5} + \begin{cppcode*}{highlightlines=5} Polygon p; p.draw(); // Polygon.draw @@ -205,8 +244,7 @@ \frametitlecpp[11]{{\texttt override} keyword} \begin{block}{Principle} \begin{itemize} - \item when overriding a virtual method - \item the \cppinline|override| keyword should be used + \item when overriding a virtual method, the \cppinline|override| keyword should be used \item the \cppinline|virtual| keyword is then optional \end{itemize} \end{block} @@ -267,7 +305,7 @@ \end{advanced} \begin{frame}[fragile] - \frametitlecpp[98]{Pure Virtual methods} + \frametitlecpp[11]{Pure Virtual methods} \begin{block}{Concept} \begin{itemize} \item unimplemented methods that must be overridden @@ -278,7 +316,7 @@ \end{block} \pause \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} // Error : abstract class Shape s; @@ -358,9 +396,7 @@ delete p; // dynamic dispatch to right destructor \end{cppcode} \begin{goodpractice}{Virtual destructors} - \begin{itemize} - \item If you expect users to inherit from your class and override methods (i.e.\ use your class polymorphically), declare its destructor \cppinline{virtual} - \end{itemize} + If you expect users to inherit from your class and override methods (i.e.\ use your class polymorphically), declare its destructor \cppinline{virtual} \end{goodpractice} \end{frame} @@ -378,9 +414,10 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} struct Drawable { - virtual ~Drawable() = default; + virtual ~Drawable() + = default; virtual void draw() = 0; } \end{cppcode*} @@ -402,7 +439,7 @@ \item unless you inherit them using \cppinline{using} \end{itemize} \end{block} - \begin{cppcode*}{gobble=0} + \begin{cppcode*}{} struct BaseClass { virtual int foo(std::string); virtual int foo(int); @@ -420,7 +457,7 @@ \frametitlecpp[98]{Polymorphism} \begin{exercise}{Polymorphism} \begin{itemize} - \item go to code/polymorphism + \item go to \texttt{exercises/polymorphism} \item look at the code \item open trypoly.cpp \item create a Pentagon, call its perimeter method @@ -450,7 +487,7 @@ \end{tikzpicture} \columnbreak \vspace{2cm} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class TextBox : public Rectangle, Text { // inherits from both @@ -505,7 +542,7 @@ \end{block} \begin{multicols}{2} \begin{tikzpicture}[] - \classbox[below of=title]{Drawable}{} + \classbox{Drawable}{} \classbox[below left of=Drawable,node distance=2cm]{Rectangle}{} \classbox[right of=Rectangle,node distance=3cm]{Text}{} \classbox[below right of=Rectangle,node distance=2cm]{TextBox}{} @@ -533,16 +570,18 @@ \frametitlecpp[98]{Multiple inheritance advice} \begin{goodpractice}{Avoid multiple inheritance} \begin{itemize} - \item Except for inheriting from interfaces - \item And for rare special cases - \end{itemize} - \end{goodpractice} - \pause - \begin{goodpractice}{Absolutely avoid diamond-shaped inheritance} - \begin{itemize} - \item This is a sign that your architecture is not correct - \item In case you are tempted, think twice and change your mind + \item Except for inheriting from interfaces (=no data members) + \item And for rare special cases \end{itemize} + + \hspace*{0.05\textwidth}\begin{minipage}{0.9\textwidth} + \begin{alertblock}{Absolutely avoid diamond-shape inheritance} + \begin{itemize} + \item This is a sign that your architecture is not correct + \item In case you are tempted, think twice and change your mind + \end{itemize} + \end{alertblock} + \end{minipage} \end{goodpractice} \end{frame} @@ -550,7 +589,7 @@ \frametitlecpp[98]{Virtual inheritance} \begin{exerciseWithShortcut}{Virtual inheritance}{Virtual OO} \begin{itemize} - \item go to code/virtual\_inheritance + \item go to \texttt{exercisescode/virtual\_inheritance} \item look at the code \item open trymultiherit.cpp \item create a TextBox and call draw diff --git a/talk/objectorientation/allocations.tex b/talk/objectorientation/allocations.tex index 0d4462b9..94d267ca 100644 --- a/talk/objectorientation/allocations.tex +++ b/talk/objectorientation/allocations.tex @@ -37,7 +37,7 @@ \item each thread in a process has its own stack \begin{itemize} \item allocations on the stack are thus ``thread private'' - \item and do not introduce any thread safety issues + \item and do not introduce any thread-safety issues \end{itemize} \end{itemize} \end{block} @@ -77,7 +77,7 @@ \item there is a single, shared heap per process \begin{itemize} \item allows to share data between threads - \item introduces race conditions and thread safety issues! + \item introduces race conditions and thread-safety issues! \end{itemize} \end{itemize} \end{block} @@ -99,12 +99,12 @@ } int g() { // constructor called - MyFirstClass *a = new MyFirstClass(3); + MyFirstClass *a = new MyFirstClass{3}; } // memory leak !!! \end{cppcode} - \begin{goodpracticeWithShortcut}{Prefer smart pointers over new/delete}{Prefer smart pointer} + \begin{goodpractice}[Prefer smart pointer]{Prefer smart pointers over new/delete} Prefer smart pointers to manage objects (discussed later) - \end{goodpracticeWithShortcut} + \end{goodpractice} \end{frame} \begin{frame}[fragile] @@ -125,7 +125,7 @@ delete[] a; // destructor called 10 times } \end{cppcode} - \begin{goodpracticeWithShortcut}{Prefer containers over new-ed arrays}{Prefer containers} + \begin{goodpractice}[Prefer containers]{Prefer containers over new-ed arrays} Prefer containers to manage collections of objects (discussed later) - \end{goodpracticeWithShortcut} + \end{goodpractice} \end{frame} diff --git a/talk/objectorientation/constructors.tex b/talk/objectorientation/constructors.tex index b1a7cc2d..ef990ecc 100644 --- a/talk/objectorientation/constructors.tex +++ b/talk/objectorientation/constructors.tex @@ -1,7 +1,7 @@ \subsection[construct]{Constructors/destructors} \begin{frame}[fragile] - \frametitlecpp[98]{Class Constructors and Destructors} + \frametitlecpp[98]{Class constructors and destructors} \begin{block}{Concept} \begin{itemize} \item special functions called when building/destroying an object @@ -11,33 +11,33 @@ \end{itemize} \end{block} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} - class MyFirstClass { + \begin{cppcode*}{} + class C { public: - MyFirstClass(); - MyFirstClass(int a); - ~MyFirstClass(); + C(); + C(int a); + ~C(); ... protected: int a; }; \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=10} + \begin{cppcode*}{firstnumber=10} // note: special notation for // initialization of members - MyFirstClass() : a(0) {} + C::C() : a(0) {} - MyFirstClass(int a_):a(a_) {} + C::C(int a) : a(a) {} - ~MyFirstClass() {} + C::~C() {} \end{cppcode*} \end{multicols} \end{frame} \begin{frame}[fragile] - \frametitlecpp[98]{Class Constructors and Destructors} + \frametitlecpp[98]{Class constructors and destructors} \begin{cppcode} class Vector { public: @@ -61,22 +61,20 @@ \begin{frame}[fragile] \frametitlecpp[98]{Constructors and inheritance} \begin{cppcode} - struct MyFirstClass { + struct First { int a; - MyFirstClass(); - MyFirstClass(int a); + First() {} // leaves a uninitialized + First(int a) : a(a) {} }; - struct MySecondClass : MyFirstClass { + struct Second : First { int b; - MySecondClass(); - MySecondClass(int b); - MySecondClass(int a, int b); + Second(); + Second(int b); + Second(int a, int b); }; - MySecondClass::MySecondClass() : MyFirstClass(), b(0) {} - MySecondClass::MySecondClass(int b_) - : MyFirstClass(), b(b_) {} - MySecondClass::MySecondClass(int a_, int b_) - : MyFirstClass(a_), b(b_) {} + Second::Second() : First(), b(0) {} + Second::Second(int b) : b(b) {} // First() implicitly + Second::Second(int a, int b) : First(a), b(b) {} \end{cppcode} \end{frame} @@ -95,28 +93,48 @@ \end{block} \pause \begin{cppcode} - struct MySecondClass : MyFirstClass { - MySecondClass(); - MySecondClass(const MySecondClass &other); + struct C { + C(); + C(const C &other); }; \end{cppcode} \pause - \begin{exampleblock}{The rule of 3/5/0 (\cpp98/\cpp11 and newer) - {\color{blue!50!white} \href{https://en.cppreference.com/w/cpp/language/rule_of_three}{cppreference}}} - \begin{itemize} - \item if a class has a destructor, a copy/move constructor or a copy/move assignment operator, it should have all three/five. strive for having none by using RAII types as members. - \end{itemize} - \end{exampleblock} + \begin{goodpractice}[Rule of 3/5]{The rule of 3/5 (\cpp98/11) - \href{https://en.cppreference.com/w/cpp/language/rule_of_three}{cppreference}} + if a class needs a custom destructor, a copy/move constructor or a copy/move assignment operator, it should have all three/five. + \end{goodpractice} \end{frame} \begin{frame}[fragile] \frametitlecpp[98]{Class Constructors and Destructors} + \begin{overprint} + \onslide<1> \begin{cppcode} class Vector { public: Vector(int n); Vector(const Vector &other); ~Vector(); - ... + private: + int len; int* data; + }; + Vector::Vector(int n) : len(n) { + data = new int[n]; + } + + + + + Vector::~Vector() { delete[] data; } + \end{cppcode} + \onslide<2> + \begin{cppcode} + class Vector { + public: + Vector(int n); + Vector(const Vector &other); + ~Vector(); + private: + int len; int* data; }; Vector::Vector(int n) : len(n) { data = new int[n]; @@ -127,6 +145,8 @@ } Vector::~Vector() { delete[] data; } \end{cppcode} + + \end{overprint} \end{frame} \begin{frame}[fragile] @@ -136,10 +156,10 @@ \item A constructor with a single non-default parameter can be used by the compiler for an implicit conversion. \end{itemize} \end{block} - \begin{exampleblockGB}{Example}{https://godbolt.org/z/TvqT185fz}{Unary constructor in action} + \begin{exampleblockGB}{Example}{https://godbolt.org/z/TvqT185fz}{Unary constructor} \begin{cppcode} void print(const Vector & v) { - std::cout<<"printing v elements...\n"; + std::cout << "printing v elements...\n"; } int main { @@ -206,7 +226,7 @@ \begin{cppcode} struct Delegate { int m_i; - Delegate(int i) : m_i(i) { + explicit Delegate(int i) : m_i(i) { ... complex initialization ... } Delegate() : Delegate(42) {} @@ -219,21 +239,22 @@ \frametitlecpp[11]{Constructor inheritance} \begin{block}{Idea} \begin{itemize} - \item avoid having to re-declare parent's constructors + \item avoid having to redeclare parent's constructors \item by stating that we inherit all parent constructors \item derived class can add more constructors \end{itemize} \end{block} \begin{exampleblock}{Practically} \begin{cppcode} - struct BaseClass { - BaseClass(int a); + struct Base { + explicit Base(int a); // ctor 1 }; - struct DerivedClass : BaseClass { - using BaseClass::BaseClass; - DerivedClass(int a, int b); + struct Derived : Base { + using Base::Base; + Derived(int a, int b); // ctor 2 }; - DerivedClass a{5}; + Derived d{5}; // calls ctor 1 + Derived d{5, 6}; // calls ctor 2 \end{cppcode} \end{exampleblock} \end{frame} @@ -248,16 +269,17 @@ \end{block} \begin{exampleblock}{Practically} \begin{cppcode} - struct BaseClass { - int a{5}; // also possible: int a = 5; - BaseClass() = default; - BaseClass(int _a) : a(_a) {} + struct Base { + int a{5}; // or: int a = 5; + Base() = default; + explicit Base(int _a) : a(_a) {} }; - struct DerivedClass : BaseClass { + struct Derived : Base { int b{6}; - using BaseClass::BaseClass; + using Base::Base; }; - DerivedClass d{7}; // a = 7, b = 6 + Derived d1; // a = 5, b = 6 + Derived d2{7}; // a = 7, b = 6 \end{cppcode} \end{exampleblock} \end{frame} @@ -265,10 +287,10 @@ \begin{frame}[fragile] \frametitlecpp[11]{Calling constructors} \begin{block}{After object declaration, arguments within \{\}} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} struct A { - int a; - float b; + int i; + float f; A(); A(int); A(int, int); @@ -286,10 +308,10 @@ \begin{frame}[fragile] \frametitlecpp[98]{Calling constructors the old way} \begin{block}{Arguments are given within (), aka \cpp98 nightmare} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} struct A { - int a; - float b; + int i; + float f; A(); A(int); A(int, int); @@ -297,29 +319,21 @@ A a(1,2); // A::A(int, int) A a(1); // A::A(int) - A a(); // declaration of a function ! + A a(); // declaration of a function! A a; // A::A() - A a = (1,2); // A::A(int), comma operator ! + A a = (1,2); // A::A(int), comma operator! A a = {1,2}; // not allowed \end{cppcode*} \end{block} \end{frame} \begin{frame}[fragile] - \frametitlecpp[11]{Calling constructors for arrays and vectors} - \begin{exampleblock}{list of items given within \{\}} + \frametitlecpp[11]{Constructing arrays and vectors} + \begin{exampleblock}{List of items given within \{\}} \begin{cppcode*}{firstnumber=10} int ip[3]{1,2,3}; - int* ip = new int[3]{1,2,3}; - std::vector v{1,2,3}; + int* ip = new int[3]{1,2,3}; // not allowed in C++98 + std::vector v{1,2,3}; // same \end{cppcode*} \end{exampleblock} - \pause - \begin{block}{\cpp98 nightmare} - \begin{cppcode*}{firstnumber=10} - int ip[3]{1,2,3}; // OK - int* ip = new int[3]{1,2,3}; // not allowed - std::vector v{1,2,3}; // not allowed - \end{cppcode*} - \end{block} \end{frame} diff --git a/talk/objectorientation/functors.tex b/talk/objectorientation/functors.tex index 242b0bde..b5b1d115 100644 --- a/talk/objectorientation/functors.tex +++ b/talk/objectorientation/functors.tex @@ -26,12 +26,12 @@ \begin{frame}[fragile] \frametitlecpp[20]{Function objects} - \begin{exampleblockGB}{Function objects as function arguments}{https://godbolt.org/z/1nqnfKxx8}{function objects} + \begin{exampleblockGB}{Function objects as function arguments}{https://godbolt.org/z/zxqYG6xzT}{function objects} \begin{cppcode} int count_if(const auto& range, auto predicate) { int count = 0; // ↑ template (later) for (const auto& e : range) - count += predicate(e); + if (predicate(e)) count++; return count; } struct IsBetween { diff --git a/talk/objectorientation/inheritance.tex b/talk/objectorientation/inheritance.tex index 6645ca39..086909d5 100644 --- a/talk/objectorientation/inheritance.tex +++ b/talk/objectorientation/inheritance.tex @@ -3,7 +3,7 @@ \begin{frame}[fragile] \frametitlecpp[98]{First inheritance} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} struct MyFirstClass { int a; void squareA() { a *= a; } @@ -42,7 +42,6 @@ \memorypush{a = 2} \memorypush{b = 5} \memorystruct{1}{2}{\tiny myobj2} - \draw[-Triangle,thick] (\stacksizex-1*\stacksizey,-1*\stacksizey) node[left] {\footnotesize \cppinline{this} pointer} -- (\stacksizex,-0.1*\stacksizey); \end{tikzpicture} \end{overprint} \vfill \null @@ -58,12 +57,11 @@ \end{description} \begin{itemize} \item The default for \texttt{class} is \cppinline{private} - \item A \cppinline{struct} is just a class that defaults to \cppinline{public} access + \item The default for \texttt{struct} is \cppinline{public} \end{itemize} \end{block} - \pause \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class MyFirstClass { public: void setA(int x); @@ -74,7 +72,7 @@ }; \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=9} + \begin{cppcode*}{firstnumber=9} MyFirstClass obj; obj.a = 5; // error ! obj.setA(5); // ok @@ -94,7 +92,7 @@ Gives access to classes inheriting from base class \end{block} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class MyFirstClass { public: void setA(int a); @@ -105,7 +103,7 @@ }; \end{cppcode*} \columnbreak - \begin{cppcode*}{gobble=2,firstnumber=13} + \begin{cppcode*}{firstnumber=13} class MySecondClass : public MyFirstClass { public: @@ -166,7 +164,7 @@ \draw[very thick,-Triangle] (MyThirdClass)--(MySecondClass) node[midway,right] {public}; \end{tikzpicture} \columnbreak - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} void funcSecond() { int a = priv; // Error int b = prot; // OK @@ -208,7 +206,7 @@ \draw[very thick,-Triangle] (MyThirdClass)--(MySecondClass) node[midway,right] {public}; \end{tikzpicture} \columnbreak - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{highlightlines=14} void funcSecond() { int a = priv; // Error int b = prot; // OK @@ -250,7 +248,7 @@ \draw[very thick,-Triangle] (MyThirdClass)--(MySecondClass) node[midway,right] {public}; \end{tikzpicture} \columnbreak - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{highlightlines=8-9} void funcSecond() { int a = priv; // Error int b = prot; // OK diff --git a/talk/objectorientation/objectsclasses.tex b/talk/objectorientation/objectsclasses.tex index 1a248078..5ab314d7 100644 --- a/talk/objectorientation/objectsclasses.tex +++ b/talk/objectorientation/objectsclasses.tex @@ -7,7 +7,7 @@ \begin{itemize} \item with inheritance \item with access control - \item including methods/member functions + \item including methods (aka.\ member functions) \end{itemize} \end{block} \begin{block}{Objects} @@ -32,7 +32,7 @@ \begin{frame}[fragile] \frametitlecpp[98]{My first class} \begin{multicols}{2} - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} struct MyFirstClass { int a; void squareA() { @@ -65,70 +65,70 @@ \begin{frame}[fragile] \frametitlecpp[98]{Separating the interface} - \begin{block}{Header : MyFirstClass.hpp} - \begin{cppcode*}{} - #pragma once - struct MyFirstClass { - int a; - void squareA(); - int sum(int b); - }; - \end{cppcode*} - \end{block} - \begin{block}{Implementation : MyFirstClass.cpp} - \begin{cppcode*}{} - #include "MyFirstClass.hpp" - void MyFirstClass::squareA() { - a *= a; - } - int MyFirstClass::sum(int b) { - return a + b; - } - \end{cppcode*} - \end{block} + \begin{columns}[t] + \begin{column}{.45\textwidth} + \begin{block}{Header: MyClass.hpp} + \begin{cppcode*}{} + #pragma once + struct MyClass { + int a; + void squareA(); + }; + \end{cppcode*} + \end{block} + \begin{block}{Implementation: MyClass.cpp} + \begin{cppcode*}{} + #include "MyClass.hpp" + void MyClass::squareA() { + a *= a; + } + \end{cppcode*} + \end{block} + \end{column} + \begin{column}{.45\textwidth} + \begin{block}{User 1: main.cpp} + \begin{cppcode*}{} + #include "MyClass.hpp" + int main() { + MyClass mc; + ... + } + \end{cppcode*} + \end{block} + \begin{block}{User 2: fun.cpp} + \begin{cppcode*}{} + #include "MyClass.hpp" + void f(MyClass& mc) { + mc.squareA(); + } + \end{cppcode*} + \end{block} + \end{column} + \end{columns} \end{frame} \begin{frame}[fragile] \frametitlecpp[98]{Implementing methods} - \begin{block}{Standard practice} + \begin{goodpractice}{Implementing methods} \begin{itemize} \item usually in .cpp, outside of class declaration \item using the class name as ``namespace'' + \item short member functions can be in the header + \item some functions (templates, \cppinline{constexpr}) must be in the header \end{itemize} - \end{block} - \begin{cppcode} + \end{goodpractice} + \begin{block}{} + \begin{cppcode} + #include "MyFirstClass.hpp" + void MyFirstClass::squareA() { a *= a; } - int MyFirstClass::sum(int b) { return a + b; } - \end{cppcode} -\end{frame} - -\begin{frame}[fragile] - \frametitlecpp[98]{\texttt{this} keyword} - \begin{block}{How to know an object's address?} - \begin{itemize} - \item Sometimes we need to pass a reference to ourself to a different entity - \item For example to implement operators, see later - \item All class methods can use the keyword \cppinline{this} - \begin{itemize} - \item It returns the address of the current object - \item Its type is \cppinline{T*} in the methods of a struct/class {\ttfamily T} - \end{itemize} - \end{itemize} + \end{cppcode} \end{block} - \begin{cppcode} - void externalFunc(MyStruct & s); - - struct MyStruct { - void invokeExternalFunc() { - externalFunc(*this); // Pass a reference to ourself - } - }; - \end{cppcode} \end{frame} \begin{frame}[fragile] @@ -145,7 +145,7 @@ int a; int sum(int b); int sum(int b, int c); - } + }; int MyFirstClass::sum(int b) { return a + b; } diff --git a/talk/objectorientation/operators.tex b/talk/objectorientation/operators.tex index bfcca357..0ea892b8 100644 --- a/talk/objectorientation/operators.tex +++ b/talk/objectorientation/operators.tex @@ -7,8 +7,8 @@ float m_real, m_imaginary; Complex(float real, float imaginary); Complex operator+(const Complex& other) { - return Complex(m_real + other.m_real, - m_imaginary + other.m_imaginary); + return Complex{m_real + other.m_real, + m_imaginary + other.m_imaginary}; } }; @@ -17,28 +17,36 @@ \end{cppcode} \end{frame} -\begin{frame} +\begin{frame}[fragile] \frametitlecpp[98]{Operator overloading} \begin{block}{Defining operators for a class} \begin{itemize} \item implemented as a regular method \begin{itemize} - \item either inside the class, as a member function + \item \small either inside the class, as a member function \item or outside the class (not all) \end{itemize} - \item with a special name (replace @ by anything) - \begin{tabular}{llll} - Expression & As member & As non-member \\ - \hline - @a & (a).operator@() & operator@(a) \\ - a@b & (a).operator@(b) & operator@(a,b) \\ - a=b & (a).operator=(b) & cannot be non-member \\ - a(b...) & (a).operator()(b...) & cannot be non-member \\ - a[b] & (a).operator[](b) & cannot be non-member \\ - a-\textgreater & (a).operator-\textgreater() & cannot be non-member \\ - a@ & (a).operator@(0) & operator@(a,0) \\ - \end{tabular} + \item with a special name (replace @ by operators from below)\small \end{itemize} + \begin{tabular}{llll} + Expression & As member & As non-member \\ + \hline + @a & (a).operator@() & operator@(a) \\ + a@b & (a).operator@(b) & operator@(a,b) \\ + a=b & (a).operator=(b) & cannot be non-member \\ + a(b...) & (a).operator()(b...) & cannot be non-member \\ + a[b] & (a).operator[](b) & cannot be non-member \\ + a-\textgreater & (a).operator-\textgreater() & cannot be non-member \\ + a@ & (a).operator@(0) & operator@(a,0) \\ + \hline + \end{tabular} + + \small + \begin{cppcode*}{linenos=false} + possible operators: + - * / % ^ & | ~ ! = < > + += -= *= /= %= ^= &= |= << >> >>= <<= + == != <= >= <=> && || ++ -- , ->* -> () [] + \end{cppcode*} \end{block} \end{frame} @@ -49,7 +57,7 @@ struct Complex { float m_real, m_imaginary; Complex operator+(float other) { - return Complex(m_real + other, m_imaginary); + return Complex{m_real + other, m_imaginary}; } }; Complex c1{2.f, 3.f}; @@ -59,7 +67,7 @@ \pause \begin{cppcode*}{firstnumber=10} Complex operator+(float a, const Complex& obj) { - return Complex(a + obj.m_real, obj.m_imaginary); + return Complex{a + obj.m_real, obj.m_imaginary}; } \end{cppcode*} \end{block} @@ -86,6 +94,28 @@ \end{block} \end{frame} +\begin{frame}[fragile] + \frametitlecpp[98]{Chaining operators} + \begin{block}{In general, return a reference to the left value} + \begin{cppcode} + struct Complex { + float m_real, m_imaginary; + Complex& operator=( const Complex& other ) { + m_real = other.m_real; + m_imaginary = other.m_imaginary; + return *this; + } + }; + Complex c1{2.f, 3.f}; + Complex c2, c3; + // right to left associativity + c3 = c2 = c1; + // left to right associativity + std::cout << c1 << c2 << c3 << std::endl; + \end{cppcode} + \end{block} +\end{frame} + \begin{frame}[fragile] \frametitlecpp[98]{Friend declarations} \begin{block}{Concept} @@ -94,12 +124,12 @@ \item They gain access to all private/protected members \item Useful for operators such as \cppinline{a + b} \item Don't abuse friends to go around a wrongly designed interface - \item Avoid unexpected modifications of class state in a friend + \item Avoid unexpected modifications of class state in a friend! \end{itemize} \end{block} \begin{exampleblock}{\texttt{operator+} as a friend} \footnotesize - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} class Complex { float m_r, m_i; friend Complex operator+(Complex const & a, Complex const & b); @@ -118,7 +148,7 @@ \begin{exercise}{Operators} Write a simple class representing a fraction and pass all tests \begin{itemize} - \item go to \texttt{code/operators} + \item go to \texttt{exercises/operators} \item look at \texttt{operators.cpp} \item inspect \cppinline{main} and complete the implementation of \cppinline{class Fraction} step by step \item you can comment out parts of \cppinline{main} to test in between diff --git a/talk/objectorientation/static.tex b/talk/objectorientation/static.tex index 00cffd62..dcc0dd51 100644 --- a/talk/objectorientation/static.tex +++ b/talk/objectorientation/static.tex @@ -9,15 +9,35 @@ \item identified by the \cppinline{static} keyword \end{itemize} \end{block} + + \vspace{-1\baselineskip} + \begin{overprint} + \onslide<1> + \begin{exampleblock}{Static.hpp} \begin{cppcode} class Text { public: - static std::string upper(std::string) {...} + static std::string upper(std::string); private: static int callsToUpper; // add `inline` in C++17 }; + \end{cppcode} + \end{exampleblock} + + \onslide<2> + \begin{exampleblock}{Static.cpp} + \begin{cppcode} + #include "Static.hpp" int Text::callsToUpper = 0; // required before C++17 + + std::string Text::upper(std::string lower) { + callsToUpper++; + // convert lower to upper case + // return ...; + } std::string uppers = Text::upper("my text"); // now Text::callsToUpper is 1 \end{cppcode} + \end{exampleblock} + \end{overprint} \end{frame} diff --git a/talk/objectorientation/typecasting.tex b/talk/objectorientation/typecasting.tex index ede5176f..924dba0e 100644 --- a/talk/objectorientation/typecasting.tex +++ b/talk/objectorientation/typecasting.tex @@ -23,8 +23,8 @@ \begin{frame}[fragile] \frametitlecpp[98]{Type casting example} \small - \begin{exampleblock}{} - \begin{cppcode*}{gobble=2} + \begin{exampleblockGB}{Practically}{https://godbolt.org/z/16faqc64s}{\texttt{casting}} + \begin{cppcode*}{} struct A{ virtual ~A()=default; } a; struct B : A {} b; @@ -34,9 +34,7 @@ B& f = dynamic_cast(c); // OK, c is a B B& g = dynamic_cast(a); // throws std::bad_cast - B& foo(A& h) { - return dynamic_cast(h); - } + B& foo(A& h) { return dynamic_cast(h); } B& i = foo(c); // OK, c is a B B* j = dynamic_cast(&a); // nullptr. a not a B. @@ -44,7 +42,7 @@ // Will not reach this } \end{cppcode*} - \end{exampleblock} + \end{exampleblockGB} \end{frame} \begin{frame}[fragile] @@ -69,7 +67,7 @@ \end{block} \begin{alertblock}{Casts to avoid} \scriptsize - \begin{cppcode*}{gobble=2} + \begin{cppcode*}{} void func(A const & a) { A& ra = a; // Error: not const A& ra = const_cast(a); // Compiles. Bad design! diff --git a/talk/python/ctypes.tex b/talk/python/ctypes.tex index 63474960..296f956b 100644 --- a/talk/python/ctypes.tex +++ b/talk/python/ctypes.tex @@ -28,7 +28,7 @@ \end{cppcode*} \end{block} \begin{exampleblock}{calling the mandel library} - \begin{minted}[gobble=4]{python} + \begin{minted}{python} from ctypes import * libmandel = CDLL('libmandelc.so') v = libmandel.mandel(c_float(0.3), c_float(1.2)) @@ -40,7 +40,7 @@ \frametitle{Marrying \cpp and python} \begin{exercise}{\cpp and python} \begin{itemize} - \item go to code/python + \item go to \texttt{exercises/python} \item look at the original python code mandel.py \item time it (`time python3 mandel.py`) \item look at the code in mandel.hpp/cpp diff --git a/talk/python/marryingcandcpp.tex b/talk/python/marryingcandcpp.tex index 16dec9b6..c27bf7a1 100644 --- a/talk/python/marryingcandcpp.tex +++ b/talk/python/marryingcandcpp.tex @@ -25,7 +25,7 @@ \end{cppcode*} \end{exampleblock} \begin{block}{Binary symbols : file.o} - \begin{minted}[gobble=4]{shell} + \begin{minted}{shell} # nm file.o 000000000000001a T square 0000000000000000 T sum @@ -44,7 +44,7 @@ \end{cppcode*} \end{exampleblock} \begin{block}{Binary symbols : file.o} - \begin{minted}[gobble=4]{shell} + \begin{minted}{shell} # nm file.o 0000000000000000 T _Z3sumff 000000000000002a T _Z6squaref @@ -57,7 +57,7 @@ \frametitle{Forcing C mangling in \cpp} \begin{block}{extern ``C''} These functions will use C mangling : - \begin{cppcode*}{gobble=1} + \begin{cppcode*}{} extern "C" { float sum(float a, float b); int square(int a); diff --git a/talk/python/modulewriting.tex b/talk/python/modulewriting.tex index 44b0117d..1b78fcd8 100644 --- a/talk/python/modulewriting.tex +++ b/talk/python/modulewriting.tex @@ -11,7 +11,7 @@ \begin{frame}[fragile] \frametitle{Basic Module(1): wrap your method} - \begin{block}{mandelModule.cpp - see code/python exercise} + \begin{block}{mandelModule.cpp - see exercises/python exercise} \begin{cppcode*}{} #include #include "mandel.hpp" @@ -32,7 +32,7 @@ \begin{frame}[fragile] \frametitle{Basic Module(2): create the python module} - \begin{block}{mandelModule.cpp - see code/python exercise} + \begin{block}{mandelModule.cpp - see exercises/python exercise} \begin{cppcode*}{} // declare the modules' methods PyMethodDef mandelMethods[] = { @@ -60,8 +60,8 @@ \item with '\mintinline{bash}{-I \$(PYTHON\_INCLUDE)}' \end{itemize} \end{block} - \begin{block}{mandel.py - see code/python exercise} - \begin{minted}[gobble=4]{python} + \begin{block}{mandel.py - see exercises/python exercise} + \begin{minted}{python} from mandel import mandel v = mandel(0.7, 1.2) \end{minted} diff --git a/talk/setup.tex b/talk/setup.tex index c4cc7dec..3e1e918c 100644 --- a/talk/setup.tex +++ b/talk/setup.tex @@ -49,28 +49,12 @@ \usepackage{morewrites} \usepackage[utf8]{inputenc} \usepackage{newunicodechar} -\usepackage{scontents} -\makeatletter -\let\verbatimsc\@undefined -\let\endverbatimsc\@undefined -\makeatother \usepackage{minted} -\newminted{tex}{linenos} -\newenvironment{verbatimsc} - {\VerbatimEnvironment - \begin{minted}[linenos,escapeinside=||]{cpp}} - {\end{minted}} -\newcommand\highlightCppCode[2]{ - \renewenvironment{verbatimsc} - {\VerbatimEnvironment - \begin{minted}[linenos,highlightlines={#1},escapeinside=||]{cpp}} - {\end{minted}} - \typestored{#2} -} -\newminted{cpp}{gobble=4,linenos} -\newminted{shell-session}{gobble=4} -\newminted[makefile]{shell-session}{gobble=4} -\newminted{python}{linenos=true,gobble=4} +\newminted{tex}{linenos,gobble=4} +\newminted{cpp}{autogobble,linenos} +\newminted{shell-session}{autogobble} +\newminted[makefile]{shell-session}{autogobble} +\newminted{python}{linenos=true,autogobble} \newmintinline[cppinline]{cpp}{} \usepackage{fancyvrb} @@ -92,6 +76,7 @@ \usepackage{booktabs} \usepackage{comment} \usepackage{totcount} +\usepackage{xspace} % Use C++Course.cut for output so it's cleaned by latexmk \def\DefaultCutFileName{\def\CommentCutFile{\jobname.cut}} @@ -100,7 +85,7 @@ %%%%%%%%%%%%%%%%%%% % useful commands % %%%%%%%%%%%%%%%%%%% -\newcommand{\cpp}{C$^{++}$} +\newcommand{\cpp}{C$^{++}$\xspace} \newcommand{\deprecated}{\textcolor{red}{\bf Deprecated}} \newcommand{\removed}{\textcolor{red}{\bf Removed}} @@ -139,9 +124,11 @@ {% \end{beamerboxesrounded} } -\newenvironment{goodpractice}[1] +\newenvironment{goodpractice}[2][] {% - \begin{goodpracticeWithShortcut}{#1}{#1} + \ifthenelse{\equal{#1}{}}% + { \begin{goodpracticeWithShortcut}{#2}{#2} } + { \begin{goodpracticeWithShortcut}{#2}{#1} } }% {% \end{goodpracticeWithShortcut} @@ -185,9 +172,15 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % frametitle with C++ version % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Switch designs for advanced and essentials course: +\newif\ifisAdvancedSlide\isAdvancedSlidefalse % Use as \frametitlecpp[14]{Title} \newcommand\frametitlecpp[2][98]{ - \frametitle{#2 \hfill \cpp#1} + \ifisAdvancedSlide + \frametitle{#2 \hfill \cpp#1 \small Adv} + \else + \frametitle{#2 \hfill \cpp#1} + \fi } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -398,7 +391,7 @@ \memorystackset{ size y/.get=\stacksizey } - \draw[snake=brace,thick] (-2pt,#1*\stacksizey-\stacksizey) -- (-2pt,#2*\stacksizey) + \draw[snake=brace,thick] (-2pt,#1*\stacksizey-\stacksizey+2pt) -- (-2pt,#2*\stacksizey-2pt) node [midway, above, sloped] {#3}; } @@ -412,7 +405,7 @@ %%%%%%%%%%%%%%%%%% \title{HEP \cpp course} -\author[B. Gruber, S. Hageboeck, S. Ponce]{B. Gruber, S. Hageboeck, S. Ponce \\ \texttt{sebastien.ponce@cern.ch}} +\author[D. Chamont, B. Gruber, S. Hageboeck, S. Ponce]{D.Chamont, B. Gruber, S. Hageboeck, S. Ponce \\ \texttt{sebastien.ponce@cern.ch}} \institute{CERN} \date{\today} \pgfdeclareimage[height=0.5cm]{cernlogo}{CERN-logo.jpg} diff --git a/talk/tools/compiling.tex b/talk/tools/compiling.tex index ceacb772..5da7bee7 100644 --- a/talk/tools/compiling.tex +++ b/talk/tools/compiling.tex @@ -42,7 +42,6 @@ free and open source \item[\href{http://clang.llvm.org/}{\beamergotobutton{clang}}] drop-in replacement of gcc \\ - slightly better error reporting \\ free and open source, based on LLVM \item[\href{https://www.intel.com/content/www/us/en/developer/tools/oneapi/dpc-compiler.html\#gs.dyllp0}{\beamergotobutton{icc} \beamergotobutton{icx}}] Intel's compilers, proprietary but now free \\ @@ -70,7 +69,7 @@ \end{block} \begin{block}{Optimization} \begin{description} - \item[-g] add debug symbols + \item[-g] add debug symbols (also: \texttt{-g3} or \texttt{-ggdb}) \item[-Ox] 0 = no opt., 1-2 = opt., 3 = highly opt. (maybe larger binary), g = opt. for debugging \end{description} \end{block} @@ -182,7 +181,7 @@ find_package(ZLIB REQUIRED) # for external libs add_executable(hello main.cpp util.h util.cpp) - target_compile_features(hello PUBLIC cxx_std_17) + set(CMAKE_CXX_STANDARD 20) target_link_libraries(hello PUBLIC ZLIB::ZLIB) \end{minted} \end{block} @@ -212,7 +211,7 @@ \frametitle{Compiler chain} \begin{exercise}{Compiler chain} \begin{itemize} - \item go to code/polymorphism + \item go to \texttt{exercises/polymorphism} \item preprocess Polygons.cpp (g++ -E -o output) \item compile Polygons.o and trypoly.o (g++ -c -o output) \item use nm to check symbols in .o files diff --git a/talk/tools/debugging.tex b/talk/tools/debugging.tex index 91ed016d..84644c16 100644 --- a/talk/tools/debugging.tex +++ b/talk/tools/debugging.tex @@ -58,7 +58,7 @@ \frametitle{gdb} \begin{exercise}{gdb} \begin{itemize} - \item go to code/debug + \item go to \texttt{exercises/debug} \item compile, run, see the crash \item run it in gdb (or lldb on newer MacOS) \item inspect backtrace, variables @@ -76,6 +76,7 @@ \item windows for variables, breakpoints, call stack, active threads, watch variables in-code, disassembly, run to cursor ... \end{itemize} \begin{description} + \item[Native gdb] Try ``tui enable'' for a simple built-in UI \item[\href{https://code.visualstudio.com/docs/cpp/cpp-debug}{\beamergotobutton{VSCode}}] Built-in support for gdb \item[\href{https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb}{\beamergotobutton{CodeLLDB}}] diff --git a/talk/tools/doxygen.tex b/talk/tools/doxygen.tex index 4bd7d665..583ee3cb 100644 --- a/talk/tools/doxygen.tex +++ b/talk/tools/doxygen.tex @@ -24,7 +24,7 @@ \begin{block}{Comment blocks} \begin{columns} \begin{column}{.35\textwidth} - \begin{cppcode*}{gobble=6} + \begin{cppcode*}{} /** * text/directives ... */ @@ -37,7 +37,7 @@ \end{cppcode*} \end{column} \begin{column}{.35\textwidth} - \begin{cppcode*}{gobble=6,firstnumber=10} + \begin{cppcode*}{firstnumber=10} /// /// text/directives ... /// diff --git a/talk/tools/formatting.tex b/talk/tools/formatting.tex index 2f611466..789bb149 100644 --- a/talk/tools/formatting.tex +++ b/talk/tools/formatting.tex @@ -17,7 +17,7 @@ \item \mintinline{bash}{clang-format -i } (looks for .clang-format file) \item \mintinline{bash}{git clang-format} (formats local changes) \item \mintinline{bash}{git clang-format } (formats changes since git \textless{}ref\textgreater{}) - \item Some editors/IDEs find a .clang-format file and adapt + \item Most editors/IDEs can find a .clang-format file and adapt \end{itemize} \end{block} \end{frame} @@ -30,7 +30,7 @@ \item Format code with: \mintinline{bash}{clang-format --style=GNU -i } \item Inspect changes, try \mintinline{bash}{git diff .} \item Revert changes using \mintinline{bash}{git checkout -- } or \mintinline{bash}{git checkout .} - \item Go to code directory and create a .clang-format file \\ + \item Go to \texttt{exercises} directory and create a .clang-format file \\ \mintinline{bash}{clang-format -style=LLVM -dump-config >} \\ \mintinline{bash}{.clang-format} \item Run \mintinline{bash}{clang-format -i /*.cpp} diff --git a/talk/tools/sanitizers.tex b/talk/tools/sanitizers.tex index 3be80ab8..284803be 100644 --- a/talk/tools/sanitizers.tex +++ b/talk/tools/sanitizers.tex @@ -39,12 +39,12 @@ \begin{overprint} \onslide<1> \vfill - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} int i = *address; \end{cppcode*} \onslide<2-> \vfill - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} if (IsPoisoned(address)) { ReportError(address, kAccessSize, kIsWrite); } @@ -67,7 +67,7 @@ \begin{multicols}{2} \begin{overprint} \onslide<1> - \begin{cppcode*}{gobble=4} + \begin{cppcode*}{} void foo() { char a[8]; char b[8]; @@ -152,8 +152,8 @@ \column{\textwidth+1cm} \begin{block}{Finding memory leaks with ASan} \begin{itemize} - \item On linux, ASan can display memory leaks - \item Start executable with \mintinline{bash}{ASAN_OPTIONS=detect_leaks=1 ./myProgram} + \item On linux, ASan can display memory leaks (``LeakSanitizer'') + \item Might be active already, otherwise start executable with \mintinline{bash}{ASAN_OPTIONS=detect_leaks=1 ./myProgram} \end{itemize} \end{block} \scriptsize @@ -203,7 +203,7 @@ \frametitle{Address sanitizer (ASan)} \begin{exercise}{address sanitizer} \begin{itemize} - \item Go to \texttt{code/asan} + \item Go to \texttt{exercises/asan} \item Compile and run the program \texttt{./asan} \item There are two bugs and one memory leak. Use asan to trace them down. \end{itemize} @@ -260,3 +260,16 @@ \url{https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html} \end{block} \end{frame} + +\begin{frame}[fragile] + \frametitle{Undefined Behaviour Sanitizer (UBSan)} + \begin{exercise}{Finding evil run-time bugs} + \begin{itemize} + \item Go to \texttt{exercises/ubsan} + \item Compile and run the program following the instructions in README or in the program + \item The program should run without observable issues + \item Recompile with UBSan and see that almost every second line contains evil bugs + \end{itemize} + \end{exercise} + +\end{frame} diff --git a/talk/tools/staticanalysis.tex b/talk/tools/staticanalysis.tex index 2a311e8e..a3e4a66e 100644 --- a/talk/tools/staticanalysis.tex +++ b/talk/tools/staticanalysis.tex @@ -34,7 +34,7 @@ \frametitle{cppcheck} \begin{exercise}{cppcheck} \begin{itemize} - \item go to code/cppcheck + \item go to \texttt{exercises/cppcheck} \item compile, run, see that it works \item use valgrind: no issue \item use cppcheck, see the problem @@ -97,7 +97,7 @@ \frametitle{clang-tidy} \begin{exercise}{clang-tidy} \begin{itemize} - \item go to any example which compiles (e.g. code/cppcheck) + \item go to any example which compiles (e.g. \texttt{exercises/cppcheck}) \item \mintinline{bash}{mkdir build && cd build} \item \mintinline{bash}{cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..} \item \mintinline{bash}{clang-tidy <../file.cpp> -checks=*} diff --git a/talk/tools/valgrind.tex b/talk/tools/valgrind.tex index 668befc5..2bd53546 100644 --- a/talk/tools/valgrind.tex +++ b/talk/tools/valgrind.tex @@ -43,12 +43,12 @@ \frametitle{valgrind} \begin{exercise}{valgrind} \begin{itemize} - \item go to code/valgrind + \item go to \texttt{exercises/valgrind} \item compile, run, it should work \item run with valgrind, see the problem \item fix the problem \vspace{.3cm} - \item go back to the code/debug exercise + \item go back to the \texttt{exercises/debug} exercise \item check it with valgrind \item analyze the issue, see that the variance was biaised \item fix the issue @@ -60,7 +60,7 @@ \frametitle{memcheck} \begin{exercise}{memcheck} \begin{itemize} - \item go to code/memcheck + \item go to \texttt{exercises/memcheck} \item compile, run, it should work \item run with valgrind, see LEAK summary \item run with -{}-leak-check=full to get details @@ -92,7 +92,7 @@ \frametitle{callgrind} \begin{exercise}{callgrind} \begin{itemize} - \item go to code/callgrind + \item go to \texttt{exercises/callgrind} \item compile, run, it will be slow \item change nb iterations to 20 \item run with valgrind -{}-tool=callgrind @@ -127,7 +127,7 @@ \frametitle{helgrind} \begin{exercise}{helgrind} \begin{itemize} - \item go to code/helgrind + \item go to \texttt{exercises/helgrind} \item compile, run \item check it with valgrind. You may see strange behavior \\ or it will be perfectly fine diff --git a/talk/tools/vcs.tex b/talk/tools/vcs.tex index d0ea6eb3..95e7688b 100644 --- a/talk/tools/vcs.tex +++ b/talk/tools/vcs.tex @@ -28,7 +28,7 @@ \begin{frame}[fragile] \frametitle{Git crash course} - \begin{minted}[gobble=4]{shell-session} + \begin{minted}{shell-session} $ git init myProject Initialized empty Git repository in myProject/.git/ diff --git a/talk/tools/webtools.tex b/talk/tools/webtools.tex index b8835fa2..6f26b262 100644 --- a/talk/tools/webtools.tex +++ b/talk/tools/webtools.tex @@ -6,11 +6,10 @@ An online generic compiler with immediate feedback. Allows: \begin{itemize} - \item compiling online any code against any version of any compiler + \item trying various compilers in the browser \item inspecting the assembly generated \item use of external libraries (over 50 available !) - \item running the code produced - \item using tools, e.g. ldd, include-what-you-use, ... + \item running the produced code \item sharing small pieces of code via permanent short links \end{itemize} \end{block}