GitHunt
BA

banach-space/cpp-tutor

Code examples for tutoring modern C++

cpp-tutor

Apple Silicon
x86-Ubuntu
x86-Windows

Code examples for tutoring modern C++ (including C++ STL and LLVM ADT libraries)

Summary

This tutorial provides a quick overview of various interesting C++ features,
presented through self-contained and runnable examples. It focuses on modern
C++ (>= C++11) and the C++ STL and LLVM ADT libraries.

Lessons/examples are split into items. There is at least one runnable
example per item (i.e. a source file implementing main). The provided
examples are:

  • Concise, yet complete: while short, most files are runnable. This way,
    you can study the examples by experimenting.
  • Selective and opinionated: emphasis is put on features and corner cases
    most likely encountered when just starting with modern C++. Focus is put
    on key points rather than presenting the complete picture.
  • Clear rather than optimal: code quality is sacrificed in favour of code
    clarity and readability, e.g. in some cases you will find more comments than
    code. Also, see the disclaimer

It is assumed that you already know some basic C++ and are familiar with
object-oriented programming.

Disclaimer

The examples presented here are meant to highlight (and sometimes exploit) the
specifics and quirks of the language. To this end, the presented examples may
exhibit undefined or implementation specific behaviour, or implement solutions
that are against good coding practices. This is done for educational purposes
only and always thoroughly documented.

This repository is self-published and has not been peer-reviewed in the
traditional sense.

Platform Support

The only requirements for cpp-tutor are: a C++ compiler that support C++17 and
CMake >= 3.20. This project is regularly tested on Linux, Mac OS X and Windows against
the following configurations:

  • Linux Ubuntu 22.04 (GCC-11.4 and LLVM-14)
  • Windows (Visual Studio 17 2022)
  • Mac OS X 14.7.5 (AppleClang 15)

Please refer to the CI logs (links at the top of the page) for reference
setups.

LLVM ADT

TODO

Links:

LLVM ADT in Compiler Explorer: https://gcc.godbolt.org/z/v9cThqhjr

Usage

The items covered in this tutorial are independent and you can be
studied in any order that works for you. Once you choose an item that's of
interest to you, go through the links and code samples available below. Next,
build and run it. Most executables print to stdout.
Make sure that you understand where the output comes from, what it means and
that it matches the comments in the code.

Some examples implement undefined behaviour, contain compiler errors or code
that leads to memory leaks. Such broken or problematic parts of the code
are guarded off with preprocessor symbolic constants:

  • COMPILATION_ERROR
  • MEMORY_LEAK
  • DANGLING_REF_OR_PTR
  • RUNTIME_ERROR

Be default all symbolic constants are undefined and hence there are neither
compilation errors nor memory leaks. Play around by defining them (one at a
time), recompiling and re-running the examples. Make sure that the generated
output (or compiler errors) makes sense. Comments in the corresponding source
files might be instrumental in understanding those.

Remember to re-build and re-run the examples before and after
defining MEMORY_LEAK/DANGLING_REF_OR_PTR/RUNTIME_ERROR (defining
COMPILATION_ERROR will prevent the code from compiling, so it's not really
relevant here).

Memory leaks

If you're developing on Linux, you can use Valgrind to
get a better grasp of memory leaks implemented in some of the examples, e.g.:

$ cd <build_dir>
$ valgrind smart_pointers

(<build_dir> is the build directory used when building
the project).

On other platforms, you can use
AddressSanitizer. This has
already been integrated for you, but currently only for
clang and gcc. In order to
use the address sanitizer, set the build type to ASAN, and
run your example like this:

$ ASAN_OPTIONS=detect_leaks=1 strings_pool

Runtime errors

The special case of runtime errors requires additional explanation. In most
cases the code guarded with RUNTIME_ERRORS exhibits undefined behaviour, and
as a result anything can happen. Although often the actual behaviour can be
predicted with a good amount of accuracy (*), do bare in mind that:

  • The output will depend on the system that you use.

(*) That is because compilers, runtimes and operating systems are relatively stable.

Build Instructions

It is assumed that cpp-tutor will be built in <build-dir> and that the
top-level source directory is <source-dir>. For brevity, the build
instructions are presented for Linux only.

First, you will need to clone Google Test inside <source-dir>:

$ cd <source_dir>
$ git clone https://github.com/google/googletest.git

Next, you can build all the examples as follows:

$ cd <build-dir>
$ cmake -DCT_LLVM_INSTALL_DIR=<llvm-install-dir> <source_dir>
$ make

This will generate all the targets implemented for this project. If you want to
(re-)build a particular example, run:

$ make <example_name>

Build types

Set the CMAKE_BUILD_TYPE variable to:

  • Release to generate optimised code
  • ASAN to generate build unoptimised code, with plenty of good debug info and
    configured to be run with address sanitizer

Switching between C++ standards

The default C++ standard for the whole project is set to C++17. In order to
rebuild using C++11 or C++17, use CMAKE_CXX_STANDARD. For example, to
build in C++17 mode:

$ cd <build-dir>
$ cmake -DCMAKE_CXX_STANDARD=17 <source_dir>
$ make

This will be very helpful when looking at how certain constructs have evolved
with the language.

Disabled code

As explained elsewhere in this README.md, some of the examples
contain code disabled with preprocessor symbols. In order to enable one of
such blocks, define the corresponding preprocessor symbol by re-running CMake
like this:

$ cmake -D<PREPROCESSOR_SYMBOL>=1 .

in which PREPROCESSOR_SYMBOL is one of:

  • COMPILATION_ERROR
  • MEMORY_LEAK
  • DANGLING_REF_OR_PTR
  • RUNTIME_ERROR

This will update <build_dir>/include/cppt_ag.hpp, the CMake auto-generated
header file that will be updated with the required definition. Next, re-build
and re-run your example.

Items

The items covered in this tutorial so far (with some relevant links):

  1. strings
    • C-strings vs std::string vs std::string_view vs llvm::StringeRef
      • The underlying data-representation for C++ strings.
    • SSO (Short String Optimisation)
    • std::string vs std::string_view vs llvm::StringRef
      • Performance comparison.
    • Source files:
      • strings_1_main.cpp and strings_reverse.cpp
      • strings_2_main.cpp
      • strings_3_main.cpp
  2. Dynamic memory allocation
    • all forms of new and delete (for plain datatypes and classes)
    • dynamic array of dynamic objects (a.k.a. 2-dimensional dynamical arrays)
    • memory leaks caused by mismatch in new and delete
    • deep vs shallow copy
    • a basic memory manager implemented in terms of placement new
    • source files:
      • pointers_main.cpp
      • deep_vs_shallow.{hpp|cpp}, deep_vs_shallow_main.cpp
      • strings_pool_main.cpp, strings_pool.{cpp|hpp}, tests_strings_pool.cpp,
  3. C++ Unit testing
    • GTest and test fixtures
    • embedding GTest tests into the build system
    • source files:
      • cpp_tutor_ut_main.cpp, CMakeLists.txt, tests_strings_object.cpp, tests_strings.cpp
  4. Smart pointers
    • std::unique_ptr, std::shared_ptr, std::weak_ptr
    • std::make_unqiue and std::make_shared
    • source files:
      • smart_pointers_main.cpp
  5. L-value and R-value
    • l-value vs r-value
    • l-value reference vs l-value to const reference vs r-value reference
    • std::move vs std::forward
    • source files:
      • rvalue_vs_lvalue_main.cpp
  6. Move semantics
    • move constructor and move assign operator
    • source files
      • memory_block.cpp, memory_block_main.cpp
  7. Return Value Optimisation
    • (N)RVO
    • guaranteed copy elision (C++17)
    • source files:
      • rvo_main.cpp
  8. New kewords in C++11
    and beyond
    • const vs constexpr, nullptr, auto, decltype
    • source files:
      • const_vs_constexpr_main.cpp, null_vs_nullptr_main.cpp,
        auto_vs_decltype_main.cpp
  9. Explicit type conversion (a.k.a. casting):
    • explicit vs implicit type conversion
    • C vs C++ style casts (static_cast, dynamic_cast, reinterpret_cast,
      const_cast)
    • source files:
      • type_casting_main.cpp
  10. Initialization in modern C++
    (is bonkers)
    • init values for variables with automatic and static storage duration
    • types of initialization (direct, copy, value, list)
    • brace elision in list initialization
    • initializing aggregate types
    • various gotchas when using initializer lists
    • source files:
      • init_stack_vs_global_vars_main.cpp, init_aggregate_main.cpp,
        init_brace_elision_main.cpp, init_types_of_main.cpp,
        init_list_gotchas_main.cpp
  11. std::vector vs llvm::SmallVector
    • Source files:
      • vector_main_1.cpp, vector_main_2.cpp.
  12. Miscellaneous
    • Various items for which I am yet to identify a better place.
    • Source files: misc_main.cpp.