├── .clang-format ├── .github └── workflows │ ├── apple-silicon.yml │ ├── x86-ubuntu.yml │ └── x86-windows.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include ├── cppt_ag.hpp.in ├── cppt_tools.hpp ├── deep_vs_shallow.hpp ├── memory_block.hpp ├── strings_pool.hpp └── strings_reverse.hpp ├── src ├── CMakeLists.txt ├── auto_vs_decltype_main.cpp ├── const_vs_constexpr_main.cpp ├── copy_and_swap_idiom_main.cpp ├── cppt_tools.cpp ├── deep_vs_shallow.cpp ├── deep_vs_shallow_main.cpp ├── delete_vs_default_main.cpp ├── init_aggregate_main.cpp ├── init_brace_elision_main.cpp ├── init_list_gotchas_main.cpp ├── init_stack_vs_global_vars_main.cpp ├── init_types_of_main.cpp ├── memory_block.cpp ├── memory_block_main.cpp ├── misc_main.cpp ├── noexcept_main.cpp ├── null_vs_nullptr_main.cpp ├── override_final_main.cpp ├── pointers_main.cpp ├── rvalue_vs_lvalue_main.cpp ├── rvo_main.cpp ├── set_new_handler_example.cpp ├── smart_pointers_main.cpp ├── static_assert_main.cpp ├── strings │ ├── CMakeLists.txt │ ├── strings_1_main.cpp │ ├── strings_2_main.cpp │ ├── strings_3_main.cpp │ ├── strings_pool.cpp │ ├── strings_pool_main.cpp │ └── strings_reverse.cpp ├── test.txt ├── type_casting_main.cpp ├── vector_main_1.cpp └── vector_main_2.cpp └── test ├── cpp_tutor_ut_main.cpp ├── tests_strings.cpp └── tests_strings_pool.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: Google 3 | -------------------------------------------------------------------------------- /.github/workflows/apple-silicon.yml: -------------------------------------------------------------------------------- 1 | name: Apple Silicon 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ main ] 7 | schedule: 8 | - cron: '0 0 * * *' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: macos-latest 14 | strategy: 15 | matrix: 16 | type: [Debug, Release] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install Dependencies 20 | run: | 21 | brew update 22 | brew install llvm@19 23 | - name: Clone GTest 24 | run: git clone https://github.com/google/googletest.git 25 | - name: Build and run 26 | run: | 27 | cd $GITHUB_WORKSPACE 28 | mkdir build && cd build 29 | cmake -DCT_LLVM_INSTALL_DIR="/opt/homebrew/opt/llvm@19/" -DCMAKE_BUILD_TYPE=${{ matrix.type }} ../ 30 | make -j8 31 | ./cppTutorUT 32 | -------------------------------------------------------------------------------- /.github/workflows/x86-ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: x86-Ubuntu 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ main ] 7 | schedule: 8 | - cron: '0 0 * * *' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-22.04 14 | strategy: 15 | matrix: 16 | compiler: 17 | - { compiler: GNU, CC: gcc, CXX: g++ } 18 | - { compiler: LLVM, CC: clang, CXX: clang++ } 19 | type: [Debug, Release] 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Install Dependencies 23 | run: | 24 | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - 25 | sudo apt-add-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main" 26 | sudo apt-get update 27 | sudo apt-get install -y llvm-19 llvm-19-dev llvm-19-tools clang-19 28 | - name: Clone GTest 29 | run: git clone https://github.com/google/googletest.git 30 | - name: Build and run 31 | env: 32 | CC: ${{ matrix.compiler.CC }} 33 | CXX: ${{ matrix.compiler.CXX }} 34 | run: | 35 | cd $GITHUB_WORKSPACE 36 | mkdir build && cd build 37 | cmake -DCT_LLVM_INSTALL_DIR="/usr/lib/llvm-19/" -DCMAKE_BUILD_TYPE=${{ matrix.type }} ../ 38 | make -j8 39 | ./cppTutorUT 40 | -------------------------------------------------------------------------------- /.github/workflows/x86-windows.yml: -------------------------------------------------------------------------------- 1 | name: x86-Windows 2 | 3 | # FIXME - Thi seems to only build debug! 4 | 5 | on: 6 | push: 7 | pull_request: 8 | branches: [ main ] 9 | schedule: 10 | - cron: '0 0 * * *' 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build: 15 | runs-on: windows-latest 16 | strategy: 17 | matrix: 18 | type: [Debug, Release] 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Download LLVM 19 23 | # AFAIK, only tar.xz distributions contains LLVMConfig.cmake (as well 24 | # as many other things). This is the only tar.xz that I could find for 25 | # Windows - in the future there might be a better target. 26 | run: curl.exe -L -o llvm.tar.xz https://github.com/llvm/llvm-project/releases/download/llvmorg-19.1.7/clang+llvm-19.1.7-x86_64-pc-windows-msvc.tar.xz 27 | 28 | shell: powershell 29 | 30 | - name: Extract LLVM 31 | run: | 32 | 7z x llvm.tar.xz -oC:\temp 33 | 7z x C:\temp\llvm.tar -oC:\LLVM 34 | shell: powershell 35 | 36 | - name: Add LLVM to PATH 37 | run: echo "C:\LLVM\clang+llvm-19.1.7-x86_64-pc-windows-msvc\bin" | Out-File -Append $env:GITHUB_PATH 38 | shell: powershell 39 | 40 | - name: Verify LLVM version 41 | run: clang --version 42 | 43 | - name: Clone GTest 44 | run: git clone https://github.com/google/googletest.git 45 | - name: Build and run 46 | run: | 47 | tree "C:\LLVM\" 48 | mkdir build 49 | cmake -S . -B build/ -DCT_LLVM_INSTALL_DIR="C:/LLVM/clang+llvm-19.1.7-x86_64-pc-windows-msvc/" 50 | cmake --build build/ --config ${{ matrix.type }} 51 | build\cpp-tutor.sln 52 | # Seems to get stuck - fix 53 | # build\Debug\cppTutorUT.exe 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | project(cpp-tutor) 4 | 5 | set(CMAKE_CXX_STANDARD 17 CACHE STRING "") 6 | 7 | #=============================================================================== 8 | # 1. VERIFY LLVM INSTALLATION DIR 9 | # This is just a bit of a sanity checking. 10 | #=============================================================================== 11 | set(CT_LLVM_INSTALL_DIR "" CACHE PATH "LLVM installation directory") 12 | 13 | # 1.1 Check the "include" directory 14 | set(CT_LLVM_INCLUDE_DIR "${CT_LLVM_INSTALL_DIR}/include/llvm") 15 | if(NOT EXISTS "${CT_LLVM_INCLUDE_DIR}") 16 | message(FATAL_ERROR 17 | " CT_LLVM_INSTALL_DIR (${CT_LLVM_INCLUDE_DIR}) is invalid.") 18 | endif() 19 | 20 | # 1.2 Check that the LLVMConfig.cmake file exists (the location depends on the 21 | # OS) 22 | set(CT_VALID_INSTALLATION FALSE) 23 | 24 | # Ubuntu + Darwin 25 | if(EXISTS "${CT_LLVM_INSTALL_DIR}/lib/cmake/llvm/LLVMConfig.cmake") 26 | set(CT_VALID_INSTALLATION TRUE) 27 | endif() 28 | 29 | # Fedora 30 | if(EXISTS "${CT_LLVM_INSTALL_DIR}/lib64/cmake/llvm/LLVMConfig.cmake") 31 | set(CT_VALID_INSTALLATION TRUE) 32 | endif() 33 | 34 | if(NOT ${CT_VALID_INSTALLATION}) 35 | message(FATAL_ERROR 36 | "LLVM installation directory, (${CT_LLVM_INSTALL_DIR}), is invalid. Couldn't 37 | find LLVMConfig.cmake.") 38 | endif() 39 | 40 | #=============================================================================== 41 | # 2. LOAD LLVM CONFIGURATION 42 | # For more: http://llvm.org/docs/CMake.html#embedding-llvm-in-your-project 43 | #=============================================================================== 44 | # Add the location of LLVMConfig.cmake to CMake search paths (so that 45 | # find_package can locate it) 46 | # Note: On Fedora, when using the pre-compiled binaries installed with `dnf`, 47 | # LLVMConfig.cmake is located in "/usr/lib64/cmake/llvm". But this path is 48 | # among other paths that will be checked by default when using 49 | # `find_package(llvm)`. So there's no need to add it here. 50 | list(APPEND CMAKE_PREFIX_PATH "${CT_LLVM_INSTALL_DIR}/lib/cmake/llvm/") 51 | 52 | # The way LLVMConfigVersion.cmake is set up, it will only match MAJOR.MINOR 53 | # exactly, even if we do not specify "REQUIRED" in the statement below. 54 | # So we accept any version and do the proper ranged check below. 55 | find_package(LLVM CONFIG) 56 | 57 | # We defer the version checking to this statement 58 | if("${LLVM_VERSION_MAJOR}" VERSION_LESS 18) 59 | message(FATAL_ERROR "Found LLVM ${LLVM_VERSION_MAJOR}, but need LLVM 18 or above") 60 | endif() 61 | 62 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 63 | message(STATUS "Using LLVMConfig.cmake in: ${CT_LLVM_INSTALL_DIR}") 64 | 65 | message("LLVM STATUS: 66 | Definitions ${LLVM_DEFINITIONS} 67 | Includes ${LLVM_INCLUDE_DIRS} 68 | Libraries ${LLVM_LIBRARY_DIRS} 69 | Targets ${LLVM_TARGETS_TO_BUILD}" 70 | ) 71 | 72 | # Set the LLVM header and library paths 73 | include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) 74 | link_directories(${LLVM_LIBRARY_DIRS}) 75 | add_definitions(${LLVM_DEFINITIONS}) 76 | 77 | # APPEND COMPILER FLAGS 78 | # ===================== 79 | # Note that warnings are _expected_: 80 | # * the code deliberately doesn't follow the best coding practices, but 81 | # instead focuses on highlighting corner cases which indeed trigger warnings 82 | # * as part of the tutorial, compiler diagnostics (and indeed warnings) are 83 | # used to identify problematic code 84 | # For this reason we don't want to suppress or fix any warninga and we don't 85 | # use `-Werror` (or /WX for MSVC). Also, don't use `-Wall` as that would 86 | # enable too many checks. 87 | set(cpp-tutor_COMPILER_OPTIONS_CLANG -fcolor-diagnostics) 88 | set(cpp-tutor_COMPILER_OPTIONS_GNU -fdiagnostics-color=always) 89 | set(cpp-tutor_COMPILER_OPTIONS_MSVC) 90 | 91 | # ADD THE ADDRESS SANITIZER BUILD TYPE 92 | # ==================================== 93 | set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} 94 | CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel asan" 95 | FORCE) 96 | 97 | set(CMAKE_C_FLAGS_ASAN 98 | "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g" 99 | CACHE STRING "Flags used by the C compiler during AddressSanitizer builds." 100 | FORCE) 101 | set(CMAKE_CXX_FLAGS_ASAN 102 | "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g" 103 | CACHE STRING "Flags used by the C++ compiler during AddressSanitizer builds." 104 | FORCE) 105 | 106 | 107 | # TARGETS AND THE CORRESPONDING SOURCE FILES 108 | # ============================================== 109 | set(cppTutor_EXECUTABLES 110 | cppTutorUT 111 | vector_1 112 | pointers 113 | smart_pointers 114 | deep_vs_shallow 115 | memory_block 116 | rvalue_vs_lvalue 117 | type_casting 118 | const_vs_constexpr 119 | rvo 120 | auto_vs_decltype 121 | noexcept 122 | override_final 123 | null_vs_nullptr 124 | static_assert 125 | delete_vs_default 126 | init_stack_vs_global_vars 127 | init_aggregate 128 | init_types_of 129 | init_brace_elision 130 | init_list_gotchas 131 | misc 132 | copy_and_swap_idiom 133 | ) 134 | 135 | if (NOT WIN32) 136 | # This example leverages some lower-level/POSIX Unix logic that is not 137 | # available on Windows. 138 | # TODO: Make this work on Windows. If possible at all. 139 | list(APPEND cppTutor_EXECUTABLES vector_2) 140 | endif() 141 | 142 | set(cppTutorUT_SOURCES 143 | ${CMAKE_CURRENT_SOURCE_DIR}/src/strings/strings_reverse.cpp 144 | ${CMAKE_CURRENT_SOURCE_DIR}/src/strings/strings_pool.cpp 145 | ${CMAKE_CURRENT_SOURCE_DIR}/test/cpp_tutor_ut_main.cpp 146 | ${CMAKE_CURRENT_SOURCE_DIR}/test/tests_strings.cpp 147 | ${CMAKE_CURRENT_SOURCE_DIR}/test/tests_strings_pool.cpp 148 | ${CMAKE_CURRENT_SOURCE_DIR}/googletest/googletest/src/gtest-all.cc) 149 | 150 | set(vector_1_SOURCES 151 | ${CMAKE_CURRENT_SOURCE_DIR}/src/strings/strings_reverse.cpp 152 | ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_main_1.cpp) 153 | 154 | if (NOT WIN32) 155 | # This example leverages some lower-level/POSIX Unix logic that is not 156 | # available on Windows. 157 | # TODO: Make this work on Windows. If possible at all. 158 | set(vector_2_SOURCES 159 | ${CMAKE_CURRENT_SOURCE_DIR}/src/vector_main_2.cpp) 160 | endif() 161 | 162 | set(pointers_SOURCES 163 | ${CMAKE_CURRENT_SOURCE_DIR}/src/pointers_main.cpp) 164 | 165 | set(smart_pointers_SOURCES 166 | ${CMAKE_CURRENT_SOURCE_DIR}/src/smart_pointers_main.cpp) 167 | 168 | set(deep_vs_shallow_SOURCES 169 | ${CMAKE_CURRENT_SOURCE_DIR}/src/deep_vs_shallow.cpp 170 | ${CMAKE_CURRENT_SOURCE_DIR}/src/deep_vs_shallow_main.cpp) 171 | 172 | set(memory_block_SOURCES 173 | ${CMAKE_CURRENT_SOURCE_DIR}/src/memory_block.cpp 174 | ${CMAKE_CURRENT_SOURCE_DIR}/src/memory_block_main.cpp) 175 | 176 | set(rvalue_vs_lvalue_SOURCES 177 | ${CMAKE_CURRENT_SOURCE_DIR}/src/rvalue_vs_lvalue_main.cpp) 178 | 179 | set(type_casting_SOURCES 180 | ${CMAKE_CURRENT_SOURCE_DIR}/src/type_casting_main.cpp) 181 | 182 | set(const_vs_constexpr_SOURCES 183 | ${CMAKE_CURRENT_SOURCE_DIR}/src/const_vs_constexpr_main.cpp) 184 | 185 | set(rvo_SOURCES 186 | ${CMAKE_CURRENT_SOURCE_DIR}/src/rvo_main.cpp) 187 | 188 | set(auto_vs_decltype_SOURCES 189 | ${CMAKE_CURRENT_SOURCE_DIR}/src/auto_vs_decltype_main.cpp) 190 | 191 | set(noexcept_SOURCES 192 | ${CMAKE_CURRENT_SOURCE_DIR}/src/noexcept_main.cpp) 193 | 194 | set(override_final_SOURCES 195 | ${CMAKE_CURRENT_SOURCE_DIR}/src/override_final_main.cpp) 196 | 197 | set(null_vs_nullptr_SOURCES 198 | ${CMAKE_CURRENT_SOURCE_DIR}/src/null_vs_nullptr_main.cpp) 199 | 200 | set(static_assert_SOURCES 201 | ${CMAKE_CURRENT_SOURCE_DIR}/src/static_assert_main.cpp) 202 | 203 | set(delete_vs_default_SOURCES 204 | ${CMAKE_CURRENT_SOURCE_DIR}/src/delete_vs_default_main.cpp) 205 | 206 | set(init_stack_vs_global_vars_SOURCES 207 | ${CMAKE_CURRENT_SOURCE_DIR}/src/init_stack_vs_global_vars_main.cpp) 208 | 209 | set(init_aggregate_SOURCES 210 | ${CMAKE_CURRENT_SOURCE_DIR}/src/init_aggregate_main.cpp) 211 | 212 | set(init_brace_elision_SOURCES 213 | ${CMAKE_CURRENT_SOURCE_DIR}/src/init_brace_elision_main.cpp) 214 | 215 | set(init_types_of_SOURCES 216 | ${CMAKE_CURRENT_SOURCE_DIR}/src/init_types_of_main.cpp) 217 | 218 | set(init_list_gotchas_SOURCES 219 | ${CMAKE_CURRENT_SOURCE_DIR}/src/init_list_gotchas_main.cpp) 220 | 221 | set(misc_SOURCES 222 | ${CMAKE_CURRENT_SOURCE_DIR}/src/misc_main.cpp) 223 | 224 | set(copy_and_swap_idiom_SOURCES 225 | ${CMAKE_CURRENT_SOURCE_DIR}/src/copy_and_swap_idiom_main.cpp) 226 | 227 | 228 | # EXECUTABLES AND THE INCLUDE DIRECTORIES FOR TARGETS 229 | # =================================================== 230 | foreach( executable ${cppTutor_EXECUTABLES} ) 231 | add_executable( ${executable} 232 | ${${executable}_SOURCES} 233 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cppt_tools.cpp 234 | ) 235 | 236 | target_include_directories(${executable} PRIVATE SYSTEM 237 | ${PROJECT_BINARY_DIR}/include/ 238 | ${CMAKE_CURRENT_SOURCE_DIR}/include 239 | ) 240 | 241 | target_compile_options(${executable} PRIVATE 242 | "$<$:${cpp-tutor_COMPILER_OPTIONS_CLANG}>" 243 | "$<$:${cpp-tutor_COMPILER_OPTIONS_CLANG}>" 244 | "$<$:${cpp-tutor_COMPILER_OPTIONS_GNU}>" 245 | "$<$:${cpp-tutor_COMPILER_OPTIONS_MSVC}>") 246 | endforeach() 247 | 248 | target_link_libraries(vector_1 249 | LLVMSupport 250 | ) 251 | 252 | if (NOT WIN32) 253 | # This example leverages some lower-level/POSIX Unix logic that is not 254 | # available on Windows. 255 | # TODO: Make this work on Windows. If possible at all. 256 | target_link_libraries(vector_2 257 | LLVMSupport 258 | ) 259 | endif() 260 | 261 | # EXTRA SET-UP FOR UNIT TESTS 262 | # =========================== 263 | target_include_directories(cppTutorUT PRIVATE SYSTEM 264 | ${CMAKE_CURRENT_SOURCE_DIR}/googletest/googletest/ 265 | ${CMAKE_CURRENT_SOURCE_DIR}/googletest/googletest/include 266 | ) 267 | 268 | if(UNIX) 269 | target_link_libraries(cppTutorUT PRIVATE pthread) 270 | endif() 271 | 272 | 273 | # CONFIGURE THE CPP_TUTOR.H HEADER FILE 274 | # ===================================== 275 | option(MEMORY_LEAK "Use the implementation exhibiting memory leaks" OFF) 276 | option(COMPILATION_ERROR "Enable code that leads to compilation errors" OFF) 277 | option(DANGLING_REF_OR_PTR "Enable code that leads to dandling refs/ptrs" OFF) 278 | option(RUNTIME_ERROR "Enable code that leads to run-time errors (e.g. UB)." OFF) 279 | 280 | configure_file( 281 | ${PROJECT_SOURCE_DIR}/include/cppt_ag.hpp.in 282 | ${PROJECT_BINARY_DIR}/include/cppt_ag.hpp 283 | ) 284 | 285 | # TODO 286 | # ===================================== 287 | add_subdirectory(src) 288 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Andrzej Warzyński 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cpp-tutor 2 | ========= 3 | [![Apple Silicon](https://github.com/banach-space/cpp-tutor/actions/workflows/apple-silicon.yml/badge.svg)](https://github.com/banach-space/cpp-tutor/actions/workflows/apple-silicon.yml) 4 | [![x86-Ubuntu](https://github.com/banach-space/cpp-tutor/actions/workflows/x86-ubuntu.yml/badge.svg)](https://github.com/banach-space/cpp-tutor/actions/workflows/x86-ubuntu.yml) 5 | [![x86-Windows](https://github.com/banach-space/cpp-tutor/actions/workflows/x86-windows.yml/badge.svg)](https://github.com/banach-space/cpp-tutor/actions/workflows/x86-windows.yml) 6 | 7 | Code examples for tutoring modern C++ (including **C++ STL** and **[LLVM ADT](https://llvm.org/docs/ProgrammersManual.html#picking-the-right-data-structure-for-a-task)** libraries) 8 | 9 | Summary 10 | ------- 11 | This tutorial provides a quick overview of various interesting C++ features, 12 | presented through self-contained and runnable examples. It focuses on modern 13 | C++ (>= C++11) and the **C++ STL** and **LLVM ADT** libraries. 14 | 15 | Lessons/examples are split into [items](#items). There is at least one runnable 16 | example per item (i.e. a source file implementing `main`). The provided 17 | examples are: 18 | * **Concise, yet complete**: while short, most files are runnable. This way, 19 | you can study the examples by experimenting. 20 | * **Selective and opinionated**: emphasis is put on features and corner cases 21 | most likely encountered when _just starting with modern C++_. Focus is put 22 | on key points rather than presenting the complete picture. 23 | * **Clear rather than optimal**: code quality is sacrificed in favour of code 24 | clarity and readability, e.g. in some cases you will find more comments than 25 | code. Also, see the [disclaimer](#disclaimer) 26 | 27 | It is assumed that you already know some basic C++ and are familiar with 28 | object-oriented programming. 29 | 30 | Disclaimer 31 | ---------- 32 | The examples presented here are meant to highlight (and sometimes exploit) the 33 | specifics and quirks of the language. To this end, the presented examples may 34 | exhibit **undefined** or **implementation specific** behaviour, or implement solutions 35 | that are against good coding practices. This is done for educational purposes 36 | only and always thoroughly documented. 37 | 38 | This repository is self-published and has not been peer-reviewed in the 39 | traditional sense. 40 | 41 | Platform Support 42 | ---------------- 43 | The only requirements for **cpp-tutor** are: a C++ compiler that support C++17 and 44 | CMake >= 3.20. This project is regularly tested on Linux, Mac OS X and Windows against 45 | the following configurations: 46 | * Linux Ubuntu 22.04 (GCC-11.4 and LLVM-14) 47 | * Windows (Visual Studio 17 2022) 48 | * Mac OS X 14.7.5 (AppleClang 15) 49 | 50 | Please refer to the CI logs (links at the top of the page) for reference 51 | setups. 52 | 53 | LLVM ADT 54 | -------- 55 | TODO 56 | 57 | Links: 58 | * https://www.youtube.com/watch?v=owQlnNYek2o&ab_channel=LLVM 59 | * https://www.youtube.com/watch?v=vElZc6zSIXM&ab_channel=CppCon 60 | * https://llvm.org/devmtg/2014-04/PDFs/LightningTalks/data_structure_llvm.pdf 61 | * https://llvm.org/docs/ProgrammersManual.html#picking-the-right-data-structure-for-a-task 62 | 63 | LLVM ADT in Compiler Explorer: https://gcc.godbolt.org/z/v9cThqhjr 64 | 65 | Usage 66 | ----- 67 | The [items](#items) covered in this tutorial are independent and you can be 68 | studied in any order that works for you. Once you choose an item that's of 69 | interest to you, go through the links and code samples available below. Next, 70 | [build](#build-instructions) and run it. Most executables print to `stdout`. 71 | Make sure that you understand where the output comes from, what it means and 72 | that it matches the comments in the code. 73 | 74 | Some examples implement undefined behaviour, contain compiler errors or code 75 | that leads to memory leaks. Such _broken_ or _problematic_ parts of the code 76 | are guarded off with preprocessor symbolic constants: 77 | * `COMPILATION_ERROR` 78 | * `MEMORY_LEAK` 79 | * `DANGLING_REF_OR_PTR` 80 | * `RUNTIME_ERROR` 81 | 82 | Be default all symbolic constants are undefined and hence there are neither 83 | compilation errors nor memory leaks. Play around by defining them (one at a 84 | time), recompiling and re-running the examples. Make sure that the generated 85 | output (or compiler errors) makes sense. Comments in the corresponding source 86 | files might be instrumental in understanding those. 87 | 88 | Remember to re-build and re-run the examples _before_ and _after_ 89 | defining `MEMORY_LEAK`/`DANGLING_REF_OR_PTR`/`RUNTIME_ERROR` (defining 90 | `COMPILATION_ERROR` will prevent the code from compiling, so it's not really 91 | relevant here). 92 | 93 | ### Memory leaks 94 | If you're developing on Linux, you can use [Valgrind](http://valgrind.org/) to 95 | get a better grasp of memory leaks implemented in some of the examples, e.g.: 96 | ```bash 97 | $ cd 98 | $ valgrind smart_pointers 99 | ``` 100 | (`` is the build directory used when [building](#build-instructions) 101 | the project). 102 | 103 | On other platforms, you can use 104 | [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html). This has 105 | already been integrated for you, but currently only for 106 | [clang](https://clang.llvm.org/) and [gcc](https://gcc.gnu.org/). In order to 107 | use the address sanitizer, set the [build type](#build-types) to `ASAN`, and 108 | run your example like this: 109 | ```bash 110 | $ ASAN_OPTIONS=detect_leaks=1 strings_pool 111 | ``` 112 | 113 | ### Runtime errors 114 | The special case of runtime errors requires additional explanation. In most 115 | cases the code guarded with `RUNTIME_ERRORS` exhibits undefined behaviour, and 116 | as a result anything can happen. Although often the actual behaviour can be 117 | predicted with a good amount of accuracy (*), do bare in mind that: 118 | * _The output will depend on the system that you use._ 119 | 120 | (*) That is because compilers, runtimes and operating systems are relatively stable. 121 | 122 | Build Instructions 123 | ------------------ 124 | It is assumed that **cpp-tutor** will be built in `` and that the 125 | top-level source directory is ``. For brevity, the build 126 | instructions are presented for Linux only. 127 | 128 | First, you will need to clone Google Test inside ``: 129 | ```bash 130 | $ cd 131 | $ git clone https://github.com/google/googletest.git 132 | ``` 133 | 134 | Next, you can build all the examples as follows: 135 | ```bash 136 | $ cd 137 | $ cmake -DCT_LLVM_INSTALL_DIR= 138 | $ make 139 | ``` 140 | This will generate all the targets implemented for this project. If you want to 141 | (re-)build a particular example, run: 142 | ```bash 143 | $ make 144 | ``` 145 | 146 | ### Build types 147 | Set the `CMAKE_BUILD_TYPE` variable to: 148 | * `Release` to generate optimised code 149 | * `ASAN` to generate build unoptimised code, with plenty of good debug info and 150 | configured to be run with address sanitizer 151 | 152 | ### Switching between C++ standards 153 | The default C++ standard for the whole project is set to C++17. In order to 154 | rebuild using `C++11` or `C++17`, use `CMAKE_CXX_STANDARD`. For example, to 155 | build in `C++17` mode: 156 | ```bash 157 | $ cd 158 | $ cmake -DCMAKE_CXX_STANDARD=17 159 | $ make 160 | ``` 161 | This will be very helpful when looking at how certain constructs have evolved 162 | with the language. 163 | 164 | ### Disabled code 165 | As explained [elsewhere](#usage) in this README.md, some of the examples 166 | contain code disabled with preprocessor symbols. In order to enable one of 167 | such blocks, define the corresponding preprocessor symbol by re-running CMake 168 | like this: 169 | ```bash 170 | $ cmake -D=1 . 171 | ``` 172 | 173 | in which `PREPROCESSOR_SYMBOL` is one of: 174 | * `COMPILATION_ERROR` 175 | * `MEMORY_LEAK` 176 | * `DANGLING_REF_OR_PTR` 177 | * `RUNTIME_ERROR` 178 | 179 | This will update `/include/cppt_ag.hpp`, the CMake auto-generated 180 | header file that will be updated with the required definition. Next, re-build 181 | and re-run your example. 182 | 183 | Items 184 | ----- 185 | The items covered in this tutorial so far (with some relevant links): 186 | 1. [strings](https://embeddedartistry.com/blog/2017/7/24/stdstring-vs-c-strings) 187 | * C-strings vs `std::string` vs `std::string_view` vs `llvm::StringeRef` 188 | * The underlying data-representation for C++ strings. 189 | * SSO ([Short String Optimisation](https://akrzemi1.wordpress.com/2014/04/14/common-optimizations/)) 190 | * `std::string` vs `std::string_view` vs `llvm::StringRef` 191 | * Performance comparison. 192 | * Source files: 193 | * `strings_1_main.cpp` and `strings_reverse.cpp` 194 | * `strings_2_main.cpp` 195 | * `strings_3_main.cpp` 196 | 2. [Dynamic memory allocation](https://en.wikipedia.org/wiki/C_dynamic_memory_allocation) 197 | * all forms of `new` and `delete` (for plain datatypes and classes) 198 | * dynamic array of dynamic objects (a.k.a. 2-dimensional dynamical arrays) 199 | * memory leaks caused by mismatch in `new` and `delete` 200 | * deep vs shallow copy 201 | * a basic memory manager implemented in terms of `placement new` 202 | * source files: 203 | * `pointers_main.cpp` 204 | * `deep_vs_shallow.{hpp|cpp}`, `deep_vs_shallow_main.cpp` 205 | * `strings_pool_main.cpp`, `strings_pool.{cpp|hpp}`, `tests_strings_pool.cpp`, 206 | 3. [C++ Unit testing](https://github.com/google/googletest) 207 | * GTest and test fixtures 208 | * embedding GTest tests into the build system 209 | * source files: 210 | * `cpp_tutor_ut_main.cpp`, `CMakeLists.txt`, `tests_strings_object.cpp`, `tests_strings.cpp` 211 | 4. [Smart pointers](https://docs.microsoft.com/en-us/cpp/cpp/smart-pointers-modern-cpp?view=vs-2017) 212 | * `std::unique_ptr`, `std::shared_ptr`, `std::weak_ptr` 213 | * `std::make_unqiue` and `std::make_shared` 214 | * source files: 215 | * `smart_pointers_main.cpp` 216 | 5. [L-value and R-value](https://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c/) 217 | * l-value vs r-value 218 | * l-value reference vs l-value to const reference vs r-value reference 219 | * `std::move` vs `std::forward` 220 | * source files: 221 | * `rvalue_vs_lvalue_main.cpp` 222 | 6. [Move semantics](https://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html) 223 | * move constructor and move assign operator 224 | * source files 225 | * `memory_block.cpp`, `memory_block_main.cpp` 226 | 7. [Return Value Optimisation](https://en.wikipedia.org/wiki/Copy_elision#Return_value_optimization) 227 | * (N)RVO 228 | * guaranteed copy elision (C++17) 229 | * source files: 230 | * `rvo_main.cpp` 231 | 8. [New kewords in C++11](https://www.codeproject.com/Articles/570638/Ten-Cplusplus11-Features-Every-Cplusplus-Developer) 232 | and [beyond](https://github.com/AnthonyCalandra/modern-cpp-features) 233 | * `const` vs `constexpr`, `nullptr`, `auto`, `decltype` 234 | * source files: 235 | * `const_vs_constexpr_main.cpp`, `null_vs_nullptr_main.cpp`, 236 | `auto_vs_decltype_main.cpp` 237 | 9. [Explicit type conversion](https://www.learncpp.com/cpp-tutorial/4-4a-explicit-type-conversion-casting/) (a.k.a. casting): 238 | * explicit vs implicit type conversion 239 | * C vs C++ style casts (`static_cast`, `dynamic_cast`, `reinterpret_cast`, 240 | `const_cast`) 241 | * source files: 242 | * `type_casting_main.cpp` 243 | 10. [Initialization in modern C++](https://www.youtube.com/watch?v=SCoewvXablk) 244 | (is [bonkers](https://blog.tartanllama.xyz/initialization-is-bonkers/)) 245 | * init values for variables with automatic and static storage duration 246 | * types of initialization (direct, copy, value, list) 247 | * brace elision in list initialization 248 | * initializing aggregate types 249 | * various gotchas when using initializer lists 250 | * source files: 251 | * `init_stack_vs_global_vars_main.cpp`, `init_aggregate_main.cpp`, 252 | `init_brace_elision_main.cpp`, `init_types_of_main.cpp`, 253 | `init_list_gotchas_main.cpp` 254 | 11. [std::vector](https://en.cppreference.com/w/cpp/container/vector) vs [llvm::SmallVector](https://llvm.org/docs/ProgrammersManual.html#llvm-adt-smallvector-h) 255 | * Source files: 256 | * `vector_main_1.cpp`, `vector_main_2.cpp`. 257 | 12. Miscellaneous 258 | * Various items for which I am yet to identify a better place. 259 | * Source files: `misc_main.cpp`. 260 | -------------------------------------------------------------------------------- /include/cppt_ag.hpp.in: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // cppt_ag.hpp.in 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Auto-generated defines exported from CMake. 10 | // 11 | // License: MIT 12 | //======================================================================== 13 | #cmakedefine MEMORY_LEAK 14 | #cmakedefine COMPILATION_ERROR 15 | #cmakedefine RUNTIME_ERROR 16 | #cmakedefine DANGLING_REF_OR_PTR 17 | -------------------------------------------------------------------------------- /include/cppt_tools.hpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // cppt_tools.hpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Prototypes for helper functions and definitions required by pretty much 10 | // every file in the project. 11 | // 12 | // License: MIT 13 | //============================================================================== 14 | // A workaround for GCC-9 on MacOS, as described here: 15 | // https://github.com/Homebrew/homebrew-core/issues/40676 16 | // TODO Remove once fixed in HomeBrew or GCC 17 | #ifdef __APPLE__ 18 | #if __GNUC__ >= 9 19 | #include 20 | #endif 21 | #endif 22 | 23 | namespace cppt{ 24 | void header(const char* filename); 25 | void footer(const char* filename); 26 | } 27 | -------------------------------------------------------------------------------- /include/deep_vs_shallow.hpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // deep_vs_shallow.hpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Declaration of Shallow and Deep used in deep_vs_shallow_main.cpp. Both 10 | // classes implement a tiny/dummy buffer of ints, the only difference 11 | // between the two being: 12 | // - Shallow implements a copy constructor that does shallow copying 13 | // - Deep implements a copy constructor that does deep copying 14 | // 15 | // License: MIT 16 | //======================================================================== 17 | #ifndef _DEEP_VS_SHALLOW_ 18 | #define _DEEP_VS_SHALLOW_ 19 | 20 | #include 21 | 22 | class Shallow { 23 | public: 24 | explicit Shallow(); 25 | Shallow(const Shallow &rhs); 26 | ~Shallow(); 27 | void add(int elem); 28 | int &getElemAt(unsigned idx); 29 | 30 | private: 31 | int *m_data = nullptr; 32 | constexpr static size_t m_capacity = 10; 33 | size_t m_num_elements = 0; 34 | }; 35 | 36 | class Deep { 37 | public: 38 | explicit Deep(); 39 | Deep(const Deep &rhs); 40 | ~Deep(); 41 | void add(int elem); 42 | int &getElemAt(unsigned idx); 43 | 44 | private: 45 | int *m_data = nullptr; 46 | constexpr static size_t m_capacity = 10; 47 | size_t m_num_elements = 0; 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /include/memory_block.hpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // memory_block.hpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Illustrates a basic memory manager with a move-constructor and move-assign 10 | // operator. As per: 11 | // https://stackoverflow.com/questions/18085383/c11-rvalue-reference-calling-copy-constructor-too 12 | // move-constructor and move-assign-operator need to be marked as noexcept. 13 | // It's a good exercise to remove noexcept and re-run this code (i.e. the 14 | // move special functions). 15 | // 16 | // Originally published here: 17 | // https://docs.microsoft.com/en-us/cpp/cpp/move-constructors-and-move-assignment-operators-cpp?view=vs-2017 18 | // 19 | // License: MIT 20 | //======================================================================== 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | class MemoryBlock { 27 | public: 28 | // Simple constructor that initializes the resource. 29 | explicit MemoryBlock(size_t length); 30 | // Copy constructor. 31 | MemoryBlock(const MemoryBlock &other); 32 | // Copy assignment operator. 33 | MemoryBlock &operator=(const MemoryBlock &other); 34 | // Move constructor. 35 | MemoryBlock(MemoryBlock &&other) noexcept; 36 | // Move assignment operator. 37 | MemoryBlock &operator=(MemoryBlock &&other) noexcept; 38 | // Destructor. 39 | ~MemoryBlock() ; 40 | 41 | // Retrieves the length of the data resource. 42 | size_t length() const; 43 | 44 | private: 45 | // The length of the resource. 46 | size_t m_length; 47 | // The resource. 48 | int *m_data; 49 | }; 50 | -------------------------------------------------------------------------------- /include/strings_pool.hpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // strings_pool.hpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // 10 | // License: MIT 11 | //============================================================================== 12 | #ifndef _STRINGS_POOL_ 13 | #define _STRINGS_POOL_ 14 | #include 15 | 16 | //============================================================================== 17 | // StringsPool class 18 | // 19 | // A basic memory manager/container for std::string. It stores strings inside a 20 | // preallocated memory pool using `placement new`. It is assumes that only 21 | // small strings are stored [1], so that no dynamic memory allocation within 22 | // string's constructor is used. 23 | // 24 | // [1] Strings that fit into the static internal buffer. See also SSO. 25 | //============================================================================== 26 | class StringsPool { 27 | public: 28 | StringsPool(); 29 | StringsPool(char const *const *c_strings, size_t n); 30 | ~StringsPool(); 31 | 32 | // Since a non-default destructor was declared, the following special 33 | // functions need to be either defined or deleted. Otherwise they will be 34 | // auto-generated. 35 | StringsPool(const StringsPool &) = delete; 36 | StringsPool(StringsPool &&) = delete; 37 | StringsPool operator=(StringsPool) = delete; 38 | StringsPool operator=(StringsPool &&) = delete; 39 | 40 | // Get the string at index `idx` 41 | std::string const &at(size_t idx) const { return memory_[idx]; } 42 | // Reserve space for `request` strings 43 | void reserve(size_t request); 44 | // Add `new_str` to the memory pool 45 | void append(std::string const &new_str); 46 | // Get the number of strings currently stored 47 | size_t size() const { return size_; } 48 | 49 | private: 50 | // Allocates a new memory for capacity_ strings. All currently stored strings 51 | // are copied into the new memory pool. Old memory pool is deallocated. 52 | void reserve(); 53 | // Destroys all strings stored in memory_ and frees the memory. 54 | void destroy(); 55 | 56 | // Number of strings currently stored 57 | size_t size_ = 0; 58 | // Number of strings that can be stored inside memory_ 59 | size_t capacity_ = 0; 60 | // The raw memory pool used for storing strings 61 | std::string *memory_ = nullptr; 62 | }; 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /include/strings_reverse.hpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // strings_reverse.hpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Functions for reversing C and C++ strings - prototypes. 10 | // 11 | // License: MIT 12 | //======================================================================== 13 | #ifndef _STRINGS_ 14 | #define _STRINGS_ 15 | 16 | #include 17 | 18 | // Reverse a C string 19 | void reverse_c_str(char *input_str); 20 | // Reverse a C++ string (based on std::swap) 21 | void reverse_cpp_str_swap(std::string *input_str); 22 | // Rerverse a C++ string (based on std::reverse) 23 | void reverse_cpp_str_alg(std::string *input_str); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(strings) 2 | -------------------------------------------------------------------------------- /src/auto_vs_decltype_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // auto_vs_decltype.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Examples to demonstrate auto and decltype. Note that type deduction for 10 | // auto works very similar to type deduction for functions template. The 11 | // only notable difference is std::initializer_list<>. As per § 14.8.2.5/5 12 | // of the C++11 standard, std::initliazer_list<> is a non-deduced context 13 | // for a template argument. However with auto, § 7.1.6.4/6 has explicit 14 | // support for std::initializer_list<> 15 | // 16 | // Experiment by: 17 | // * (un-)defining COMPILATION_ERROR 18 | // * (un-)defining DANGLING_REF_OR_PTR 19 | // and checking the behaviour before and after. 20 | // 21 | // References: 22 | // * http://thbecker.net/articles/auto_and_decltype/section_01.html 23 | // 24 | // License: MIT 25 | //======================================================================== 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "cppt_ag.hpp" 33 | 34 | //======================================================================== 35 | // Helper objects/functions 36 | //======================================================================== 37 | // AFAIK the result of multiplication is never an lvalue and hence decltype 38 | // never gives SomeType&, but SomeType instead. 39 | template 40 | auto multiply(T lhs, S rhs) -> decltype(lhs * rhs) { 41 | return lhs * rhs; 42 | } 43 | 44 | // The return value of the tenary operator "?" might be an lvalue (e.g. when 45 | // both arguments are l-values) and if expr is an lvalue, then decltype(expr) 46 | // is SomeType&. Therefore, without std::remove_reference the following is 47 | // potentially returning a ref to a local variable. This example was originally 48 | // discussed here: 49 | // * http://thbecker.net/articles/auto_and_decltype/section_08.html 50 | template 51 | auto min_func(T x, S y) -> 52 | #ifndef DANGLING_REF_OR_PTR 53 | typename std::remove_reference::type { 54 | #else 55 | decltype(x < y ? x : y) { 56 | #endif 57 | return x < y ? x : y; 58 | } 59 | 60 | //======================================================================== 61 | // main 62 | //======================================================================== 63 | int main() { 64 | // 1. AUTO INSTEAD OF ITERATOR SPECIFIER 65 | std::vector vect_1(2); 66 | std::vector vect_2(2); 67 | 68 | // 1.1 Old style ... 69 | std::cout << "POPULATE vect_1:" << std::endl; 70 | for (std::vector::iterator it = vect_1.begin(); it != vect_1.end(); 71 | ++it) { 72 | std::cin >> *it; 73 | } 74 | 75 | // 1.2 ... vs with auto 76 | std::cout << "POPULATE vect_2:" << std::endl; 77 | for (auto it = vect_2.begin(); it != vect_2.end(); ++it) { 78 | std::cin >> *it; 79 | } 80 | 81 | // 2. AUTO INSTEAD OF TYPE SPECIFIER FOR SCALAR VARS 82 | int x = 42; 83 | const int &crx = x; 84 | assert(crx == x); 85 | 86 | // 2.1 Auto without ref - reference and top level const specifier are 87 | // _ignored_ 88 | auto something = 89 | crx; // something is an int, intialised with crx, that is not const 90 | assert(something == x && crx == x); 91 | something = 43; 92 | assert(something == 43 && crx == 42 && x == 42); 93 | 94 | // 2.2 Auto with ref - reference and top level const specifier are _preserved_ 95 | const int y = 13; 96 | auto &some_other_thing = y; 97 | assert(some_other_thing == y); 98 | #ifdef COMPILATION_ERROR 99 | some_other_thing = 44; 100 | #endif 101 | 102 | // 2.3 Auto with ref and const - reference and top level const specifier are 103 | // _preserved_ 104 | const auto &some_other_other_thing = 105 | x; // some_other_thing is a const int&, intialised with x 106 | assert(some_other_other_thing == x && crx == x); 107 | #ifdef COMPILATION_ERROR 108 | some_other_other_thing = 43; 109 | #endif 110 | x = 43; 111 | assert(some_other_other_thing == 43 && crx == 43 && x == 43); 112 | 113 | // 3. DECLTYPE (MULTIPLY USES DECLTYPE) 114 | // 3.1 Function return type using decltype 115 | auto some_var_1 = multiply(10u, 10u); 116 | std::cout << "Type of some_var_1: " << typeid(some_var_1).name() << std::endl; 117 | 118 | auto some_var_2 = multiply(10, 10); 119 | std::cout << "Type of some_var_2: " << typeid(some_var_2).name() << std::endl; 120 | 121 | auto some_var_3 = multiply(10u, 10.0f); 122 | std::cout << "Type of some_var_3: " << typeid(some_var_3).name() << std::endl; 123 | 124 | auto some_var_4 = multiply(10u, 10.0); 125 | std::cout << "Type of some_var_4: " << typeid(some_var_4).name() << std::endl; 126 | 127 | // 3.2 Types aliases using decltype 128 | int z = 10; 129 | const int cz = 42; 130 | const int &crz = x; 131 | 132 | using z_type = decltype(z); 133 | using cz_type = decltype(cz); 134 | using crz_type = decltype(crz); 135 | 136 | z_type var_1 = 10; 137 | cz_type var_2 = var_1; 138 | assert(var_1 == var_2); 139 | #ifdef COMPILATION_ERROR 140 | // Can't modify var_2, it's a constant (decltype preserves that) 141 | var_2 = 10; 142 | #endif 143 | 144 | crz_type var_3 = var_1; 145 | assert(var_1 == 10 && var_2 == 10 && var_3 == 10); 146 | #ifdef COMPILATION_ERROR 147 | // Can't modify var_3, it's a constant (decltype preserves that) 148 | var_3 = 100; 149 | #endif 150 | 151 | // Can modify var_1, it's not a constant 152 | var_1 = 100; 153 | assert(var_1 == 100 && var_2 == 10 && var_3 == 100); 154 | 155 | // 3.3 Dangling ref when using decltype for functions 156 | // Always safe 157 | std::cout << multiply(z, crz) << std::endl; 158 | // Check the implementation for comments, but this might be returning a ref 159 | // to a local variable (both z and crz are l-values). 160 | std::cout << min_func(z, crz) << std::endl; 161 | } 162 | -------------------------------------------------------------------------------- /src/const_vs_constexpr_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // const_vs_constepxr.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Discusses the constexpr keyword. Note that constexpr was introduced in 10 | // C++11, but only in C++14 the specification was relaxed enough to make 11 | // it practical to write constexpr functions. A very good overview can be 12 | // found Ansel Sermersheim here (CopperSpice videos by Barbara Geller and 13 | // https://www.youtube.com/watch?v=Dpob2KsYLWs 14 | // 15 | // License: MIT 16 | //======================================================================== 17 | #include 18 | 19 | //======================================================================== 20 | // Helper objects 21 | //======================================================================== 22 | //------------------------------------------------------------------------ 23 | // The following 2 functions calculate gcd (greatest common divisor) at compile 24 | // time. 25 | //------------------------------------------------------------------------ 26 | // C++11 implementation - only one return statement allowed, no loops, etc. 27 | constexpr int gcd_cpp11(int a, int b) { 28 | return (b == 0) ? a : gcd_cpp11(b, a % b); 29 | } 30 | 31 | // C++14 implementation - many restrictions are relaxed, e.g. loops and auto 32 | // are now allowed 33 | #if __cplusplus >= 201403L 34 | constexpr auto gcd_cpp14(int a, int b) { 35 | while (b != 0) { 36 | auto t = b; 37 | b = a % b; 38 | a = t; 39 | } 40 | return a; 41 | } 42 | #endif 43 | 44 | //------------------------------------------------------------------------ 45 | // The following functions implement the Fibonacci sequence calculation. The 46 | // 1st implementation uses templates, and the 2nd constexpr. 47 | //------------------------------------------------------------------------ 48 | template 49 | struct fibonacci_tmp { 50 | static const unsigned value = 51 | fibonacci_tmp::value + fibonacci_tmp::value; 52 | }; 53 | 54 | template <> 55 | struct fibonacci_tmp<0> { 56 | static const unsigned value = 0; 57 | }; 58 | 59 | template <> 60 | struct fibonacci_tmp<1> { 61 | static const unsigned value = 1; 62 | }; 63 | 64 | constexpr unsigned fibonacci_cxp(const unsigned x) { 65 | return x <= 1 ? x : fibonacci_cxp(x - 1) + fibonacci_cxp(x - 2); 66 | } 67 | 68 | //======================================================================== 69 | // main 70 | //======================================================================== 71 | int main() { 72 | // 1. CONST VS COMPILE-TIME-CONST 73 | // Integers with const qualifiers are compile time constants. Doubles aren't 74 | // and require constexpr to become compile time consts. 75 | const int var_a = 76 | 10; // can be evaluated at compile time (i.e. compile time constant) 77 | #ifdef COMPILATION_ERROR 78 | const double var_b = 79 | 3.8; // cannot be evaluated at compile time (i.e. run-time constant) 80 | #else 81 | constexpr double var_b = 82 | 3.8; // can be evaluated at compile time (i.e. compile time constant) 83 | #endif 84 | 85 | const int var_c = var_a + 5; // compile time constant 86 | constexpr double var_d = var_b - 2.7; // compile time constant 87 | 88 | // 2. CONST vs CONSTEXPR for pointers 89 | const int *ptr_e; 90 | constexpr int *ptr_f = nullptr; // equivalent to int *const ptr_f (as opposed 91 | // to const int *ptr_f) 92 | 93 | // 3. GCD 94 | // Compile time calculations 95 | constexpr int gcd_1 = gcd_cpp11(11, 121); 96 | #if __cplusplus >= 201403L 97 | constexpr int gcd_2 = gcd_cpp14(11, 121); 98 | #endif 99 | std::cout << "GCD: compile-time" << std::endl; 100 | std::cout << " gcd_cpp11(11, 121): " << gcd_1 << std::endl; 101 | #if __cplusplus >= 201403L 102 | std::cout << " gcd_cpp14(11, 121): " << gcd_2 << std::endl; 103 | #endif 104 | 105 | // Runtime calculations 106 | int a = 11; 107 | int b = 121; 108 | int gcd_3 = gcd_cpp11(a, b); 109 | #if __cplusplus >= 201403L 110 | int gcd_4 = gcd_cpp14(a, b); 111 | #endif 112 | 113 | std::cout << "GCD: run-time" << std::endl; 114 | std::cout << " gcd_cpp11(a, b): " << gcd_3 << std::endl; 115 | #if __cplusplus >= 201403L 116 | std::cout << " gcd_cpp14(a, b): " << gcd_4 << std::endl; 117 | #endif 118 | 119 | // 4. FIBONACCI 120 | // Compile time calculations 121 | constexpr unsigned c = 10; 122 | constexpr unsigned fib_1 = fibonacci_tmp::value; 123 | constexpr unsigned fib_2 = fibonacci_cxp(c); 124 | 125 | std::cout << "FIBONACCI: compile-time" << std::endl; 126 | std::cout << " fibonacci_tmp: " << fib_1 << std::endl; 127 | std::cout << " fibonacci_cxp(c): " << fib_2 << std::endl; 128 | 129 | // Runtime calculations 130 | unsigned d = 10; 131 | unsigned fib_3 = fibonacci_cxp(d); 132 | 133 | std::cout << "FIBONACCI: run-time" << std::endl; 134 | std::cout << " fibonacci_cxp(d): " << fib_3 << std::endl; 135 | } 136 | -------------------------------------------------------------------------------- /src/copy_and_swap_idiom_main.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // copy_and_swap_idiom_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Basic example of the "copy and swap" idiom: 10 | // * https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap 11 | // 12 | // This idiom helps implement a copy operation that provides strong 13 | // exception safety. The key idea is to ensure that the original object is 14 | // not modified or destroyed until a new object is successfully constructed. 15 | // 16 | // The process involves two steps: 17 | // 1. Construct a new object using RAII (this step may throw). 18 | // 2. If the first step succeeds, use the non-throwing swap method to 19 | // exchange resources with the current object. 20 | // 21 | // License: MIT 22 | //============================================================================== 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | //============================================================================== 31 | // Data types 32 | //============================================================================== 33 | // Custom "string" class - primarily to demonstrate the "copy and swap" idiom. 34 | class ct_string { 35 | // The string itself 36 | char *p{}; 37 | std::size_t length{}; 38 | 39 | public: 40 | // -------------------------------------------------------------------------- 41 | // Constructors + destructors 42 | // -------------------------------------------------------------------------- 43 | ct_string() = default; 44 | // Ctor that takes a C string. 45 | ct_string(const char *s) : length(std::strlen(s)) { 46 | p = new char[length + 1]; 47 | std::copy(s, s + length, p); 48 | p[length] = '\0'; 49 | } 50 | // Destructor 51 | ~ct_string() { delete[] p; } 52 | 53 | // Idiomatic _copy ctor_. 54 | ct_string(const ct_string &other) 55 | : p{new char[other.length + 1]}, length(other.length) { 56 | std::copy(other.p, other.p + other.length, p); 57 | p[length] = '\0'; 58 | } 59 | 60 | // Idiomatic copy-assignment operator - leverages the copy ctor. 61 | ct_string &operator=(const ct_string &other) { 62 | ct_string{other}.swap(*this); 63 | return *this; 64 | } 65 | // Idiomatic move ctor - builds on top of std::exchange: 66 | // * https://en.cppreference.com/w/cpp/utility/exchange 67 | ct_string(ct_string &&other) noexcept 68 | : p{std::exchange(other.p, nullptr)}, 69 | length{std::exchange(other.length, 0)} {} 70 | 71 | // -------------------------------------------------------------------------- 72 | // Overloaded operators 73 | // -------------------------------------------------------------------------- 74 | char operator[](std::size_t n) const { return p[n]; } 75 | char &operator[](std::size_t n) { return p[n]; } 76 | 77 | friend std::ostream &operator<<(std::ostream &os, const ct_string &other) { 78 | os << other.p << std::endl; 79 | return os; 80 | } 81 | 82 | // Idiomatic copy assignment operator 83 | ct_string &operator=(ct_string &&other) noexcept { 84 | ct_string{std::move(other)}.swap(*this); 85 | return *this; 86 | } 87 | 88 | // Bad implementation of copy assignment operator. 89 | ct_string &bad_copy_asign(ct_string &other) { 90 | char *q = new char[other.length + 1]; 91 | // Good, old storage is only deleted _after_ new storage has been 92 | // allocated. 93 | delete[] p; 94 | // Bad, what if p == q!? 95 | p = q; 96 | std::copy(other.p, other.p + other.length, p); 97 | length = other.length; 98 | p[length] = '\0'; 99 | return *this; 100 | } 101 | 102 | // -------------------------------------------------------------------------- 103 | // Various helpers 104 | // -------------------------------------------------------------------------- 105 | void swap(ct_string &other) noexcept { 106 | std::swap(p, other.p); 107 | std::swap(length, other.length); 108 | } 109 | 110 | bool empty() const { return length == 0; } 111 | }; 112 | 113 | //============================================================================== 114 | // main 115 | //============================================================================== 116 | int main(int argc, char *argv[]) { 117 | cppt::header(argv[0]); 118 | 119 | ct_string ct_string_1 = "Hello, world!\n"; 120 | ct_string ct_string_2 = "Goodbye, world!\n"; 121 | 122 | std::cout << "ct_string_1 before: " << std::endl; 123 | std::cout << ct_string_1; 124 | 125 | std::cout << "ct_string_1 after BAD assignment (ct_string_1 = ct_string_2): " 126 | << std::endl; 127 | ct_string_1 = ct_string_1.bad_copy_asign(ct_string_2); 128 | std::cout << ct_string_1; 129 | 130 | // This will, counter intuitively, print an empty string. 131 | std::cout << "ct_string_1 after BAD assignment (ct_string_1 = ct_string_1): " 132 | << std::endl; 133 | ct_string_1 = ct_string_1.bad_copy_asign(ct_string_1); 134 | std::cout << ct_string_1; 135 | 136 | std::cout << "ct_string_2 before: " << std::endl; 137 | std::cout << ct_string_2; 138 | 139 | std::cout << "ct_string_2 after GOOD assignment (ct_string_1 = ct_string_1): " 140 | << std::endl; 141 | ct_string_2 = ct_string_2; 142 | std::cout << ct_string_2; 143 | 144 | cppt::footer(argv[0]); 145 | } 146 | -------------------------------------------------------------------------------- /src/cppt_tools.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // cpp_tutor_helper.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Helper functions - implementation. 10 | // 11 | // License: MIT 12 | //======================================================================== 13 | #include "../include/cppt_tools.hpp" 14 | 15 | #include 16 | 17 | void cppt::header(const char* filename) { 18 | std::cout << "-------------------------------------" << std::endl; 19 | std::cout << "FILENAME: " << filename << std::endl << "CPP TUTOR: execution begins" << std::endl; 20 | std::cout << "-------------------------------------" << std::endl << std::endl; 21 | } 22 | 23 | void cppt::footer(const char* filename) { 24 | std::cout << std::endl; 25 | std::cout << "-------------------------------------" << std::endl; 26 | std::cout << "CPP TUTOR: execution successful!" << std::endl; 27 | std::cout << "-------------------------------------" << std::endl; 28 | } 29 | -------------------------------------------------------------------------------- /src/deep_vs_shallow.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // deep_vs_shallow.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Implementation of Deep and Shallow 10 | // 11 | // License: MIT 12 | //======================================================================== 13 | #include 14 | #include 15 | 16 | Shallow::Shallow() { m_data = new int[m_capacity]; } 17 | 18 | Shallow::Shallow(const Shallow& rhs) { m_data = rhs.m_data; } 19 | 20 | Shallow::~Shallow() { 21 | if (m_data != nullptr) { 22 | delete[] m_data; 23 | m_data = nullptr; 24 | } 25 | } 26 | 27 | void Shallow::add(int elem) { 28 | if (m_num_elements >= m_capacity) { 29 | std::cout << "Full buffer, exiting." << std::endl; 30 | return; 31 | } 32 | 33 | m_data[m_num_elements] = elem; 34 | m_num_elements++; 35 | } 36 | 37 | int& Shallow::getElemAt(unsigned idx) { return m_data[idx]; } 38 | 39 | Deep::Deep() { m_data = new int[m_capacity](); } 40 | 41 | Deep::Deep(const Deep& rhs) { 42 | m_data = new int[m_capacity]; 43 | for (size_t ii = 0; ii < m_capacity; ii++) { 44 | m_data[ii] = rhs.m_data[ii]; 45 | } 46 | } 47 | 48 | Deep::~Deep() { delete[] m_data; } 49 | 50 | void Deep::add(int elem) { 51 | if (m_num_elements >= m_capacity) { 52 | std::cout << "Full buffer, exiting." << std::endl; 53 | return; 54 | } 55 | 56 | m_data[m_num_elements] = elem; 57 | m_num_elements++; 58 | } 59 | 60 | int& Deep::getElemAt(unsigned idx) { return m_data[idx]; } 61 | -------------------------------------------------------------------------------- /src/deep_vs_shallow_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // deep_vs_shallow_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Shows the difference between deep and shallow copy. Try to guess what's 10 | // going to be printed. Then study compare that against the actual output 11 | // and make sure that you understand any discrepancies. 12 | // 13 | // License: MIT 14 | //======================================================================== 15 | #include 16 | 17 | #include 18 | #include "cppt_ag.hpp" 19 | 20 | int main() { 21 | // 1. SHALLOW COPY 22 | Shallow obj_a; 23 | for (unsigned ii = 0; ii < 10; ii++) { 24 | obj_a.add(ii); 25 | } 26 | 27 | Shallow *obj_b = new Shallow(obj_a); 28 | obj_b->getElemAt(0) = 100; 29 | 30 | for (unsigned ii = 0; ii < 10; ii++) { 31 | std::cout << obj_a.getElemAt(ii) << " "; 32 | } 33 | std::cout << std::endl; 34 | 35 | // 2. DEEP COPY 36 | Deep obj_c; 37 | for (unsigned ii = 0; ii < 10; ii++) { 38 | obj_c.add(ii); 39 | } 40 | 41 | Deep *obj_d = new Deep(obj_c); 42 | obj_d->getElemAt(0) = 100; 43 | 44 | for (unsigned ii = 0; ii < 10; ii++) { 45 | std::cout << obj_c.getElemAt(ii) << " "; 46 | } 47 | std::cout << std::endl; 48 | 49 | // FREE MEMORY 50 | delete obj_b; 51 | delete obj_d; 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /src/delete_vs_default_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // delete_vs_default_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Demonstrates the =delete and =default mechanisms. 10 | // 11 | // Experiment by: 12 | // * (un-)defining COMPILATION_ERROR 13 | // and checking the compiler errors before and after. 14 | // 15 | // 16 | // License: MIT 17 | //======================================================================== 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "cppt_ag.hpp" 24 | 25 | //======================================================================== 26 | // Helper objects/functions 27 | //======================================================================== 28 | class Shape { 29 | public: 30 | // Default constructor 31 | explicit Shape(std::string name) : type(std::move(name)) {} 32 | 33 | // Default copyt operations 34 | Shape(const Shape&) = default; 35 | Shape& operator=(const Shape&) = default; 36 | ~Shape() = default; 37 | 38 | // No move operations 39 | Shape(Shape&&) = delete; 40 | Shape& operator=(Shape&&) = delete; 41 | 42 | std::string type; 43 | }; 44 | 45 | inline bool operator==(const Shape& lhs, const Shape& rhs) { 46 | return (lhs.type == rhs.type); 47 | } 48 | 49 | inline bool operator!=(const Shape& lhs, const Shape& rhs) { 50 | return !(lhs == rhs); 51 | } 52 | 53 | //======================================================================== 54 | // main 55 | //======================================================================== 56 | int main() { 57 | Shape square("square"); 58 | Shape triangle("triangle"); 59 | // Verify that square and triangle are indeed two different objects 60 | assert(square != triangle); 61 | 62 | // Default'ed copy intialisation 63 | Shape square_2(square); 64 | assert(square == square_2); 65 | 66 | // Default'ed copy assignment 67 | triangle = square; 68 | assert(square == triangle); 69 | 70 | #ifdef COMPILATION_ERROR 71 | // Deleted move construction and assignment 72 | Shape square_3 = std::move(square_2); 73 | Shape square_4(std::move(square_2)); 74 | #endif 75 | } 76 | -------------------------------------------------------------------------------- /src/init_aggregate_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // init_aggregate_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Demonstrates aggregate initialization in modern C++. 10 | // 11 | // Experiment by: 12 | // * (un-)defining RUNTIME_ERROR 13 | // * (un-)defining COMPILATION_ERROR 14 | // 15 | // License: MIT 16 | //======================================================================== 17 | #include "cppt_ag.hpp" 18 | #include 19 | 20 | //======================================================================== 21 | // Helper objects 22 | //======================================================================== 23 | // Foo has a constructor and hence is non-aggregate type. It's member variables 24 | // are uninitialized. 25 | class Foo { 26 | public: 27 | Foo() {} 28 | int get_i() { return i; } 29 | double get_j() { return j; } 30 | 31 | private: 32 | int i; 33 | double j; 34 | }; 35 | 36 | // Bar is an aggregate type in C++14 and higher (it has in-member class 37 | // initializers, which makes it non-aggregate in C++11). It's member variables 38 | // are always initialized. 39 | class Bar { 40 | public: 41 | int get_i() { return i; } 42 | double get_j() { return j; } 43 | 44 | // Default member initializers - since C++11 45 | int i = 0; 46 | double j = 0; 47 | }; 48 | 49 | // Baz is an aggregate type. It's member variables are uninitialized. 50 | class Baz { 51 | public: 52 | int get_i() { return i; } 53 | double get_j() { return j; } 54 | 55 | int i; 56 | double j; 57 | }; 58 | 59 | // This instance of Foo is guaranteed to be zero-initialised. 60 | static Foo global_foo; 61 | 62 | //======================================================================== 63 | // main 64 | //======================================================================== 65 | int main() { 66 | // 1. AGGREGATE INITIALISATION FOR ARRAYS 67 | int array_i[4] = {0, 1, 2, 3}; 68 | int array_j[] = {0, 1, 2, 3}; 69 | // Guaranteed to be zero-initialised 70 | int array_z[13] = {}; 71 | 72 | // 2. AGGREGATE INITIALISATION FOR CLASSES 73 | #if __cplusplus >= 201403L 74 | // Aggregate initialisation (only for aggregate types) 75 | Bar bar = {1, 3.14}; 76 | #else 77 | // Default initialisation. Recall that in C++11 having in-class member 78 | // initializers makes a struct/class non-aggregate. 79 | Bar bar; 80 | #endif 81 | 82 | #ifdef COMPILATION_ERROR 83 | // Foo is non-aggregate type (it has a constructor) - this won't work 84 | Foo foo = {1, 2}; 85 | #else 86 | Foo foo; 87 | #endif 88 | 89 | // 3. PARTIAL AGGREGATE INITIALISATION FOR CLASSES 90 | // This line provides the init value for Baz::i only ... 91 | Baz baz = {1}; 92 | // ... but now Baz::j is guaranteed to be zero-initialised. 93 | double j = baz.get_j(); 94 | 95 | // 4. IMPLICIT AGGREGATE INITIALIZATION 96 | #ifdef RUNTIME_ERROR 97 | // Foo's member variables are _almost_ always (see below) undefined. Reading 98 | // them is UB. 99 | return foo.get_i(); 100 | #else 101 | // Unlike foo, global_foo (and it's member variables) is guaranteed to be 102 | // zero-initialised, because it has static storage duration (it's a global 103 | // var). 104 | return global_foo.get_i(); 105 | #endif 106 | } 107 | -------------------------------------------------------------------------------- /src/init_brace_elision_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // init_brace_elision_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Demonstrates how brace elision in aggregate initialisation works. 10 | // Although it's an interesting syntactic sugar, it's consider to have 11 | // negative impact on code clarity and is best avoided. 12 | // 13 | // Relevant links: 14 | // * https://en.cppreference.com/w/cpp/language/aggregate_initialization 15 | // 16 | // License: MIT 17 | //======================================================================== 18 | 19 | //======================================================================== 20 | // Helper objects 21 | //======================================================================== 22 | struct Foo { 23 | int a; 24 | struct Bar { 25 | int i; 26 | int j; 27 | int k[3]; 28 | } b; 29 | }; 30 | 31 | //======================================================================== 32 | // main 33 | //======================================================================== 34 | int main() { 35 | // Aggregate initialisation 36 | Foo foo_1 = {1, {2, 3, {4, 5, 6}}}; 37 | // Aggregate initialisation with brace elision (result same as above) 38 | Foo foo_2 = {1, 2, 3, 4, 5, 6}; 39 | 40 | // Aggregate initialisation (without the assignment operator) 41 | Foo foo_3{1, {2, 3, {4, 5, 6}}}; 42 | 43 | // Aggregate initialisation (without the assignment operator) with brace 44 | // elision (result same as above). 45 | // NOTE: Oddly, the following wasn't valid until C++14, but both GCC and 46 | // Clang allow it even in C++11 mode. 47 | Foo foo_4{1, 2, 3, 4, 5, 6}; 48 | } 49 | -------------------------------------------------------------------------------- /src/init_list_gotchas_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // init_list_gotchas_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Demonstrates gotchas related to std::initializer_list 10 | // 11 | // Experiment by: 12 | // * (un-)defining RUNTIME_ERROR 13 | // * (un-)defining COMPILATION_ERROR 14 | // 15 | // License: MIT 16 | //======================================================================== 17 | #include "cppt_ag.hpp" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | //======================================================================== 25 | // Helper objects 26 | //======================================================================== 27 | //----------------------------------------------------------------------------- 28 | // Classes for exploiting {} 29 | //----------------------------------------------------------------------------- 30 | template 31 | struct FooBar { 32 | FooBar() { 33 | std::cout << " FooBar()" << std::endl; 34 | }; 35 | FooBar(std::initializer_list list) { 36 | std::cout << " FooBar(std::initializer_list)" << std::endl; 37 | } 38 | }; 39 | 40 | //======================================================================== 41 | // main 42 | //======================================================================== 43 | int main() { 44 | // 1. WHAT DO THESE ACTUALLY MEAN? 45 | std::vector v1{3, 0}; // {3, 0} 46 | std::vector v2(3, 0); // {0, 0, 0} 47 | 48 | std::string s1(48, 'a'); // "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 49 | std::string s2{48, 'a'}; // "0a" 50 | 51 | // 3 strings or 1 string equal "3"? 52 | std::vector v{3}; 53 | std::cout << "Size of v: " << v.size() << std::endl; 54 | 55 | // 2. EMPTY BRACES {} ARE SPECIAL! 56 | // Calls the default ctor even though there's a constructor taking std::initlizier_list 57 | FooBar foo_bar1{}; 58 | // Calls the ctor taking std::initializer_list 59 | FooBar foo_bar2{1}; 60 | 61 | // 3. NO NARROWING CONVERSION 62 | #ifdef COMPILATION_ERROR 63 | int var_k{2.0}; 64 | #endif 65 | 66 | // 4. NESTED BRACES 67 | // Nice case 68 | std::map my_map {{"abc", 1}, {"def", 2}}; 69 | 70 | // Evil case 71 | std::vector v3 = {{"abc"}, {"def"}}; 72 | #ifdef RUNTIME_ERROR 73 | std::vector v4 = {{"abc", "def"}}; 74 | std::cout << v4[1] << std::endl; 75 | #endif 76 | 77 | // 5. AUTO AND STD::INITIALIZER_LIST 78 | // This is an int 79 | auto some_int{3}; 80 | // This looks like an int 81 | auto some_other_int = {3}; 82 | 83 | // This is an int 84 | int yet_another_int = some_int; 85 | #ifdef COMPILATION_ERROR 86 | // This would be fine provided that some_other_int was indeed an int 87 | int yet_some_other_int = some_other_int; 88 | #endif 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/init_stack_vs_global_vars_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // init_stack_vs_global_vars_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Demonstrates what happens if there's no initialization for plain ints, 10 | // and what's the difference between stack (local) and non-stack (global and 11 | // static) variables in this context. 12 | // 13 | // Variables with static storage duration (global vars and vars decorated 14 | // with the `static` keyword) are guaranteed to be zero-initialised. Local 15 | // variables often appear to be zero-initialized, but that's just a 16 | // by-product of where such variables are stored (the runtime stack) and the 17 | // state of the runtime stack (usually zero-ed at the start of a program). 18 | // 19 | // Note that although the behaviour presented here is deterministic, it 20 | // depends on the runtime libraries used. Hence it might change when using 21 | // different compiler versions. The compilers that I tested this file with 22 | // are listed in README.md, and the compiler flags can be found in 23 | // CMakeLists.txt. 24 | // 25 | // Experiment by: 26 | // * (un-)defining RUNTIME_ERROR 27 | // and re-running this file with and without Valgrind (the behaviour won't 28 | // change, but there will be much more output with Valgrind). 29 | // 30 | // Relevant links: 31 | // * https://en.cppreference.com/w/cpp/language/storage_duration 32 | // 33 | // License: MIT 34 | //======================================================================== 35 | #include "cppt_ag.hpp" 36 | #include 37 | 38 | //======================================================================== 39 | // Helper objects 40 | //======================================================================== 41 | // fez will fill the runtime stack with some numbers. 42 | void fez() { 43 | int a[5] = {1, 2, 3, 4, 5}; 44 | } 45 | 46 | // bar and baz are deliberately identical. They both exhibit UB because local_i 47 | // is uninitialised. However, if the random value on the stack used for storing 48 | // local_i is indeed 0, then the assert will indeed return true. 49 | void bar() { 50 | int local_i; 51 | assert((local_i == 0)); 52 | } 53 | 54 | void baz() { 55 | int local_i; 56 | assert(local_i == 0); 57 | } 58 | 59 | // vaz is almost identical to bar and baz, but now local_i has static storage 60 | // duration and hence is guaranteed to be zero-initialized. No UB here. 61 | void vaz() { 62 | static int local_i; 63 | assert(local_i == 0); 64 | } 65 | 66 | // Static initialisation - this variable is guaranteed to be initialised to 0 67 | static int global_i; 68 | 69 | //======================================================================== 70 | // main 71 | //======================================================================== 72 | int main() { 73 | int local_i; 74 | int local_j; 75 | 76 | // This assert is always true 77 | assert(global_i == 0); 78 | #ifdef RUNTIME_ERROR 79 | // This following line/assert exhibits UB as local_i is uninitialized: 80 | // * compilers will very likely complain (may require -Wall) 81 | // * Valgrind will identify this as an error 82 | // * assert will probably pass as the stack (and local_i is a stack 83 | // variable) is fairly clean at this point 84 | assert(local_i == 0); 85 | #endif 86 | 87 | #ifdef RUNTIME_ERROR 88 | // Same as above - calling bar yields UB: 89 | // * compilers will very likely complain (may require -Wall) 90 | // * Valgrind will identify this as an error 91 | // * assert in bar will probably pass as the stack (and local_i in bar is a 92 | // stack variable) is fairly clean at this point 93 | bar(); 94 | #endif 95 | 96 | // Calling fez `pollutes` the stack with it's local vars. 97 | fez(); 98 | 99 | #ifdef RUNTIME_ERROR 100 | // Similar to the previous two UBs: 101 | // * compilers will very likely complain (may require -Wall) 102 | // * Valgrind will identify this as an error 103 | // * assert will very likely fail as the stack memory used for local_i in 104 | // baz has been polluted by local variables in fez (no longer used, but 105 | // there's no requirement to `zero` that memory) 106 | baz(); 107 | #endif 108 | 109 | // No UB here. 110 | vaz(); 111 | 112 | return 0; 113 | } 114 | -------------------------------------------------------------------------------- /src/init_types_of_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // init_types_of_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Goes through various types of initialization: 10 | // * direct initialization (also most vexing parse) 11 | // * copy initialization 12 | // * value initialization (since C++03) 13 | // * list initialization (since C++11) 14 | // 15 | // Experiment by: 16 | // * (un-)defining RUNTIME_ERROR 17 | // * (un-)defining COMPILATION_ERROR 18 | // 19 | // Relevant links: 20 | // * https://en.cppreference.com/w/cpp/language/direct_initialization 21 | // * https://en.cppreference.com/w/cpp/language/copy_initialization 22 | // * https://en.cppreference.com/w/cpp/language/value_initialization 23 | // * https://en.cppreference.com/w/cpp/language/list_initialization 24 | // 25 | // License: MIT 26 | //======================================================================== 27 | #include "cppt_ag.hpp" 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | //======================================================================== 34 | // Helper objects 35 | //======================================================================== 36 | struct Foo { 37 | Foo(int arg_i, double arg_j) : i(arg_i), j(arg_j) {} 38 | int get_i() { return i; } 39 | double get_j() { return j; } 40 | 41 | int i; 42 | double j; 43 | }; 44 | 45 | struct Bar { 46 | // Cannot be used for copy initialization. 47 | explicit Bar(int arg_i) : i(arg_i) { 48 | std::cout << " Bar(int)" << std::endl; 49 | } 50 | #ifndef COMPILATION_ERROR 51 | // Can be used for copy initialization. 52 | Bar(double arg_j) : j(arg_j) { 53 | std::cout << " Bar(double)" << std::endl; 54 | } 55 | #endif 56 | Bar(Foo f) { 57 | std::cout << " Bar(Foo)" << std::endl; 58 | } 59 | 60 | int i; 61 | double j; 62 | }; 63 | 64 | //----------------------------------------------------------------------------- 65 | // Classes for exploiting value initialisation 66 | //----------------------------------------------------------------------------- 67 | // Value initialization either calls the user-provided constructor or does 68 | // zero-initialization. 69 | struct Baz_without_ctor { 70 | // No user provided ctor. Value initialization by zero-initialization. 71 | int i; 72 | }; 73 | 74 | Baz_without_ctor get_baz_without_ctor() { 75 | return Baz_without_ctor(); // Value initialisation - i zero-initialised 76 | } 77 | 78 | struct Baz_with_ctor { 79 | // User provided default ctor - value initialization through this ctor 80 | Baz_with_ctor() {}; 81 | int i; 82 | }; 83 | 84 | Baz_with_ctor get_baz_with_ctor() { 85 | return Baz_with_ctor(); // Value initialisation - i is uninitialised 86 | } 87 | 88 | struct Baz_with_default_ctor { 89 | // No user provided ctor. Value initialization by zero-initialization. 90 | Baz_with_default_ctor() = default; 91 | int i; 92 | }; 93 | 94 | Baz_with_default_ctor get_baz_with_default_ctor() { 95 | return Baz_with_default_ctor(); // Value initialisation - i is zero-initialised 96 | } 97 | 98 | struct Baz_with_default_ctor_ool { 99 | // Out-of-line constructor counts as user provided constructor. This _will_ 100 | // be used for value initialization. 101 | // EXPLANATION: This constructor could be defined in another TU (or a header 102 | // file), so the compiler must assume that it's user provided. This is 103 | // despite the fact that it could be marked with =default at the point of 104 | // definition (and it is indeed). 105 | Baz_with_default_ctor_ool(); 106 | int i; 107 | }; 108 | 109 | // As discussed at the point of declaring this constructor, the compiler 110 | // regards this as user-provided despite "=default" being used. 111 | Baz_with_default_ctor_ool::Baz_with_default_ctor_ool() = default; 112 | 113 | Baz_with_default_ctor_ool get_baz_with_default_ctor_ool() { 114 | return Baz_with_default_ctor_ool(); // Value initialisation - i is uninitialised 115 | } 116 | 117 | //----------------------------------------------------------------------------- 118 | // Classes for exploiting {} 119 | //----------------------------------------------------------------------------- 120 | template 121 | struct FooBar { 122 | FooBar(); 123 | FooBar(std::initializer_list list) {} 124 | }; 125 | 126 | //======================================================================== 127 | // main 128 | //======================================================================== 129 | int main() { 130 | // 1. DIRECT INITIALIZATION 131 | // (arguments in parenthesis --> direct initialization) 132 | Foo foo(1, 3.14); 133 | int var_i(13); 134 | auto* foo_ptr = new Foo(1410, 3.14); 135 | 136 | // 2. DIRECT VS COPY INITIALIZATION 137 | // Direct initialisation 138 | Bar bar_1(10); 139 | // Copy initialization - the explicit constructor cannot be used here. 140 | Bar bar_2 = 20; 141 | 142 | // 3. (HOWEVER,) MOST VEXING PARSE 143 | // This is a function declaration - not a constructor call! Yes, C++ is 144 | // annoying. 145 | Bar bar_3(Foo()); 146 | #ifdef COMPILATION_ERROR 147 | // ... since bar_3 is _not_ in instance of Bar, this won't compile. 148 | Bar *bar_ptr = &bar_3; 149 | #endif 150 | 151 | // 3. VALUE INITIALISATION 152 | // (Either calls the user-provided constructor or does zero-initialization). 153 | int var_j = int(); // var_j is initialised to 0 154 | 155 | auto baz_1 = get_baz_with_ctor(); 156 | #ifdef RUNTIME_ERROR 157 | assert(baz_1.i == 0); // baz_1.i is uninitialized - UB 158 | #endif 159 | 160 | auto baz_2 = get_baz_without_ctor(); 161 | assert(baz_2.i == 0); // baz_2.i is initialized to 0 162 | 163 | auto baz_3 = get_baz_with_default_ctor(); 164 | assert(baz_3.i == 0); // baz_3.i is initialized to 0 165 | 166 | auto baz_4 = get_baz_with_default_ctor_ool(); 167 | #ifdef RUNTIME_ERROR 168 | assert(baz_4.i == 0); // baz_4.i is uninitialized - UB 169 | #endif 170 | 171 | // 4. LIST INITIALIZATION 172 | // (from C++11 onwards) 173 | // 4.1 Direct list-initialization 174 | Foo foo_1{1, 3.14}; 175 | 176 | // 4.2 Copy list-initialization 177 | Foo foo_2 = {1, 3.14}; 178 | 179 | // CLEANUP 180 | delete foo_ptr; 181 | } 182 | -------------------------------------------------------------------------------- /src/memory_block.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // memory_block.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // The implementation of MemoryBlock 10 | // 11 | // License: MIT 12 | //======================================================================== 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | MemoryBlock::MemoryBlock(size_t length) 19 | : m_length(length), m_data(new int[length]) { 20 | std::cout << "In MemoryBlock(size_t). length = " << m_length << "." 21 | << std::endl; 22 | } 23 | 24 | MemoryBlock::~MemoryBlock() { 25 | std::cout << "In ~MemoryBlock(). length = " << m_length << "."; 26 | 27 | if (m_data != nullptr) { 28 | std::cout << " Deleting resource."; 29 | // Delete the resource. 30 | delete[] m_data; 31 | } 32 | 33 | std::cout << std::endl; 34 | } 35 | 36 | MemoryBlock::MemoryBlock(const MemoryBlock &other) 37 | : m_length(other.m_length), m_data(new int[other.m_length]) { 38 | std::cout << "In MemoryBlock(const MemoryBlock&). length = " << other.m_length 39 | << ". Copying resource." << std::endl; 40 | 41 | std::copy(other.m_data, other.m_data + m_length, m_data); 42 | } 43 | 44 | MemoryBlock &MemoryBlock::operator=(const MemoryBlock &other) { 45 | std::cout << "In operator=(const MemoryBlock&). length = " << other.m_length 46 | << ". Copying resource." << std::endl; 47 | 48 | if (this != &other) { 49 | // Free the existing resource. 50 | delete[] m_data; 51 | 52 | m_length = other.m_length; 53 | m_data = new int[m_length]; 54 | std::copy(other.m_data, other.m_data + m_length, m_data); 55 | } 56 | return *this; 57 | } 58 | 59 | size_t MemoryBlock::length() const { return m_length; } 60 | 61 | MemoryBlock::MemoryBlock(MemoryBlock &&other) noexcept 62 | : m_length(0), m_data(nullptr) { 63 | std::cout << "In MemoryBlock(MemoryBlock&&). length = " << other.m_length 64 | << ". Moving resource." << std::endl; 65 | 66 | // Copy the data pointer and its length from the 67 | // source object. 68 | m_data = other.m_data; 69 | m_length = other.m_length; 70 | 71 | // Release the data pointer from the source object so that 72 | // the destructor does not free the memory multiple times. 73 | other.m_data = nullptr; 74 | other.m_length = 0; 75 | } 76 | 77 | MemoryBlock &MemoryBlock::operator=(MemoryBlock &&other) noexcept { 78 | std::cout << "In operator=(MemoryBlock&&). length = " << other.m_length << "." 79 | << std::endl; 80 | 81 | if (this != &other) { 82 | // Free the existing resource. 83 | delete[] m_data; 84 | 85 | // Copy the data pointer and its length from the 86 | // source object. 87 | m_data = other.m_data; 88 | m_length = other.m_length; 89 | 90 | // Release the data pointer from the source object so that 91 | // the destructor does not free the memory multiple times. 92 | other.m_data = nullptr; 93 | other.m_length = 0; 94 | } 95 | return *this; 96 | } 97 | -------------------------------------------------------------------------------- /src/memory_block_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // memory_block_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Sample usage of MemoryBlock. 10 | // 11 | // License: MIT 12 | //======================================================================== 13 | #include "memory_block.hpp" 14 | 15 | #include "cppt_ag.hpp" 16 | #include "cppt_tools.hpp" 17 | 18 | #include 19 | 20 | int main() { 21 | // Create a vector object and add a few elements to it. 22 | std::vector v; 23 | v.push_back(MemoryBlock(25)); 24 | v.push_back(MemoryBlock(75)); 25 | 26 | // Insert a new element into the second position of the vector. 27 | v.insert(v.begin() + 1, MemoryBlock(50)); 28 | } 29 | -------------------------------------------------------------------------------- /src/misc_main.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // misc_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // * is_object_v 10 | // * sizeof for empty objects 11 | // * has_unique_object_representations_v 12 | // 13 | // License: MIT 14 | //============================================================================== 15 | #include 16 | #include 17 | 18 | #include 19 | //============================================================================== 20 | // Data types 21 | //============================================================================== 22 | // This is an empty struct, so its size should be 0, right? See below. 23 | struct Empty { 24 | }; 25 | 26 | // Would should be the size of `NonEmpty`? See below. 27 | struct NonEmpty : Empty { 28 | char a; 29 | }; 30 | 31 | struct UniqueRepr { 32 | int a, b; 33 | }; 34 | 35 | struct NonUniqueRepr { 36 | char a; 37 | int b; 38 | }; 39 | 40 | // Surely the size of A and B should be identical? 41 | struct A { 42 | short s; 43 | int n; 44 | char c; 45 | }; 46 | 47 | struct B { 48 | char c; 49 | short s; 50 | int n; 51 | }; 52 | 53 | //============================================================================== 54 | // main 55 | //============================================================================== 56 | int main(int argc, char *argv[]) { 57 | cppt::header(argv[0]); 58 | 59 | size_t sec_num = {1}; 60 | 61 | //------------------------------------------------------------------------- 62 | // 1. WHAT IS AN OBJECT IN C++? 63 | //------------------------------------------------------------------------- 64 | std::cout << sec_num++ << ". WHAT IS AN OBJECT IN C++?\n"; 65 | std::cout << "Is `int` an object? Answer: " << std::is_object_v << std::endl; 66 | // The standard calls out functions as non-objects, despite them occupying 67 | // memory (one of the criteria) 68 | std::cout << "Is function an objec? Answer: " 69 | << std::is_object_v << std::endl; 70 | 71 | //------------------------------------------------------------------------- 72 | // 2. SIZEOF EMPTY? 73 | // Empty Base Optimization (EBO), see: 74 | // * https://en.cppreference.com/w/cpp/language/ebo 75 | //------------------------------------------------------------------------- 76 | std::cout << "\n" << sec_num++ << ". SIZEOF EMPTY?\n"; 77 | // Somewhat surprisingly, the size of `Empty` is not 0! All C++ types occupy 78 | // non-zero bytes of storage by definition (even empty objects). Otherwise, 79 | // an object and its neighbor could occupy same memory address - that would be 80 | // confusing! 81 | Empty em; 82 | std::cout << "What's the size of an empty class? Answer: " << sizeof em << std::endl; 83 | // Interestingly, due to "Empty Base Optimization" (EBO), `NonEmpty` (which 84 | // inherits from `Empty`) will also have size 1 (i.e. the size of its only 85 | // member variable). 86 | NonEmpty nem; 87 | std::cout << "What's the size of an non-empty class? Answer: " << sizeof nem << std::endl; 88 | 89 | //------------------------------------------------------------------------- 90 | // 3. SIZEOF STRUCT 91 | // Due to padding, the size of A and B will be different. 92 | //------------------------------------------------------------------------- 93 | std::cout << "\n" << sec_num++ << ". SIZEOF STRUCT?\n"; 94 | A a; 95 | B b; 96 | std::cout << "What's the size of A? Answer: " << sizeof a << std::endl; 97 | std::cout << "What's the size of B? Answer: " << sizeof b << std::endl; 98 | 99 | //------------------------------------------------------------------------- 100 | // 4. UNIQUE REPRESENTATION? 101 | // The trait that's being checked here is true for types uniquely defined by 102 | // their values. This will exclude e.g. 103 | // * types requiring padding (C++ does not specify what happens to those), 104 | // and 105 | // * floating point types (due to e.g. NoN not having standard 106 | // representation). 107 | //------------------------------------------------------------------------- 108 | std::cout << "\n" << sec_num++ << ". UNIQUE REPRESENTATION?\n"; 109 | std::cout 110 | << "Does `int` have unique representation? Answer: " 111 | << std::has_unique_object_representations_v << std::endl; 112 | std::cout 113 | << "Does `float` have unique representation? Answer: " 114 | << std::has_unique_object_representations_v << std::endl; 115 | std::cout 116 | << "Does `UniqueRepr` have unique representation? Answer: " 117 | << std::has_unique_object_representations_v << std::endl; 118 | std::cout 119 | << "Does `UniqueRepr` have unique representation? Answer: " 120 | << std::has_unique_object_representations_v << std::endl; 121 | std::cout 122 | << "Does `NonUniqueRepr` have unique representation? Answer: " 123 | << std::has_unique_object_representations_v << std::endl; 124 | 125 | cppt::footer(argv[0]); 126 | } 127 | -------------------------------------------------------------------------------- /src/noexcept_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // noexcept_main.hpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Illustrates the noexcept keyword and demonstrates that std::vector has 10 | // different behavior if the type's move constructor is marked noexcept. This 11 | // reason behind this is very well explained here: 12 | // * 13 | // https://www.reddit.com/r/cpp_questions/comments/51btqw/why_does_stdvector_have_different_behavior_if_the/ 14 | // 15 | // License: MIT 16 | //======================================================================== 17 | #include 18 | #include 19 | 20 | //======================================================================== 21 | // Helper functions and data structures 22 | //======================================================================== 23 | // Throwing in foo triggers a call to std::terminate 24 | void foo() noexcept(true) { throw 13; } 25 | 26 | // Throwing in bar is fine 27 | void bar() noexcept(false) { throw 13; } 28 | 29 | // A is a trivial class with it's move constructor marked as noexcept. Note 30 | // that move-constructing means that the id of the object moved-from is 31 | // negated. 32 | struct A { 33 | // Default constructor. 34 | A() : id(++n_items) { 35 | std::cout << " A: default constructor for object: " << id << std::endl; 36 | } 37 | 38 | // Copy constructor. 39 | A(const A &other) : id(++n_items) { 40 | std::cout << " A: copy constructor for object: " << id << std::endl; 41 | } 42 | 43 | // Copy assignment operator. 44 | A &operator=(const A &other) = delete; 45 | 46 | // Move constructor. 47 | A(A &&other) noexcept { 48 | id = other.id; 49 | other.id = -other.id; 50 | std::cout << " A: moving object: " << id << std::endl; 51 | } 52 | 53 | // Move assignment operator. 54 | A &operator=(A &&other) = delete; 55 | 56 | // Destructor. 57 | ~A() { std::cout << " A: destructing object: " << id << std::endl; } 58 | 59 | private: 60 | // The id of the current object 61 | int id; 62 | // The total number of objects created 63 | static size_t n_items; 64 | }; 65 | 66 | size_t A::n_items = 0; 67 | 68 | // B is a trivial class identical to B, but it's move constructor _are_not_ 69 | // marked as noexcept.Note that move-constructing means that the id of the 70 | // object moved-from is negated. 71 | struct B { 72 | // Default constructor. 73 | B() : id(++n_items) { 74 | std::cout << " B: default constructor for object: " << id << std::endl; 75 | } 76 | 77 | // Copy constructor. 78 | B(const B &other) : id(++n_items) { 79 | std::cout << " B: copy constructor for object: " << id << std::endl; 80 | } 81 | 82 | // Copy assignment operator. 83 | B &operator=(const B &other) = delete; 84 | 85 | // Move constructor. 86 | B(B &&other) { 87 | id = other.id; 88 | other.id = -other.id; 89 | std::cout << " B: moving object: " << id << std::endl; 90 | } 91 | 92 | // Move assignment operator. 93 | B &operator=(B &&other) = delete; 94 | 95 | // Destructor. 96 | ~B() { std::cout << " B: destructing object: " << id << std::endl; } 97 | 98 | private: 99 | // The id of the current object 100 | int id; 101 | // The total number of objects created 102 | static size_t n_items; 103 | }; 104 | 105 | size_t B::n_items = 0; 106 | 107 | //======================================================================== 108 | // main 109 | //======================================================================== 110 | int main() { 111 | // 1. A VECTOR OF As. 112 | // The following code _will_not_ trigger any copy-constructions. Although v_A 113 | // will be resized, it's move constructors _is_ marked as noexcept and that's 114 | // what std::vector::resize will use. 115 | std::vector v_A; 116 | 117 | std::cout << "Insert element 1" << std::endl; 118 | v_A.push_back(A()); 119 | std::cout << "Insert element 2" << std::endl; 120 | v_A.push_back(A()); 121 | std::cout << "Insert element 3" << std::endl; 122 | v_A.push_back(A()); 123 | 124 | // 2. A VECTOR OF Bs. 125 | // The following code _will_ trigger copy-constructions. v_B will be 126 | // resized and B's move constructor _is_not_ marked as noexcept. 127 | std::vector v_B; 128 | 129 | std::cout << "Insert element 1" << std::endl; 130 | v_B.push_back(B()); 131 | std::cout << "Insert element 2" << std::endl; 132 | v_B.push_back(B()); 133 | std::cout << "Insert element 3" << std::endl; 134 | v_B.push_back(B()); 135 | 136 | // 3. THROW AN EXCEPTION INSIDE A FUNCTION DECORATED WITH noexcept(false) 137 | try { 138 | bar(); 139 | } catch (...) { 140 | std::cout << "Exception in bar caught" << std::endl; 141 | } 142 | 143 | // 4. THROW AN EXCEPTION INSIDE A FUNCTION DECORATED WITH noexcept(true) 144 | // Change std::terminate so that it's obvious that it's being called. 145 | std::set_terminate([]() { 146 | std::cout << "!!!! Unhandled exception !!!!\n"; 147 | std::abort(); 148 | }); 149 | 150 | try { 151 | foo(); 152 | } catch (...) { 153 | std::cout << "Exception in foo caught" << std::endl; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/null_vs_nullptr_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // null_vs_nullptr_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Demonstrates the nullptr literal and the NULL macro. When dealing with 10 | // pointers NULL, nullptr and 0 (the integer) are equivalent. However, 11 | // nullptr should be used exclusively! 12 | // 13 | // GCC and CLANG give different warnings - it is worth building with both 14 | // and comparing. 15 | // 16 | // Experiment by: 17 | // * (un-)defining COMPILATION_ERROR 18 | // and checking the compiler errors before and after. 19 | // 20 | // License: MIT 21 | //======================================================================== 22 | #include 23 | #include 24 | #include "cppt_ag.hpp" 25 | 26 | //======================================================================== 27 | // Helper functions 28 | //======================================================================== 29 | void foo(int i) { std::cout << "foo: Integer overload\n"; } 30 | void foo(int *pi) { std::cout << "foo: Pointer to integer overload\n"; } 31 | void foo(double *pd) { std::cout << "foo: Pointer to double overload\n"; } 32 | void foo(std::nullptr_t nullp) { std::cout << "foo: Null pointer overload\n"; } 33 | 34 | void bar(int i) { std::cout << "bar: Integer overload\n"; } 35 | void bar(int *pi) { std::cout << "bar: Pointer to integer overload\n"; } 36 | 37 | //======================================================================== 38 | // main 39 | //======================================================================== 40 | int main() { 41 | // 1. EQUIVALENCE OF 0,NULL AND nullptr - INTEGERS AND POINTERS 42 | // Init integer with a pointer value (i.e. NULL) 43 | int var_a = NULL; 44 | // Init pointer with an integer (i.e. 0) 45 | int *var_b = 0; 46 | assert(0 == NULL); 47 | assert(0 == nullptr); 48 | 49 | #ifdef COMPILATION_ERROR 50 | // Cannot init integer with nullptr_t 51 | int var_c = nullptr; 52 | // Cannot init a pointer with an arbitrary integer 53 | int *var_d = 10; 54 | #endif 55 | 56 | // 2. int vs NULL vs nullptr IN FUNCTION CALLS 57 | int *ptr_int; 58 | double *ptr_double; 59 | 60 | foo(var_a); 61 | foo(ptr_int); 62 | foo(ptr_double); 63 | foo(0); 64 | // Would be ambiguous without void foo(nullptr_t) 65 | foo(nullptr); 66 | #ifdef COMPILATION_ERROR 67 | // Ambiguous if NULL is an integral null pointer constant (as is the case in 68 | // most implementations) 69 | foo(NULL); 70 | #endif 71 | 72 | bar(0); 73 | #ifdef COMPILATION_ERROR 74 | // Ambiguous even though there's only one overload for pointers 75 | bar(NULL); 76 | #endif 77 | } 78 | -------------------------------------------------------------------------------- /src/override_final_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // override_final_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Demonstrate the usage of override and final. This example doesn't print 10 | // anything. Instead study the compiler errors when COMPILATION_ERROR is 11 | // defined (otherwise this example builds cleanly). 12 | // 13 | // Experiment by: 14 | // * (un-)defining COMPILATION_ERROR 15 | // and checking the compiler errors before and after. 16 | // 17 | // License: MIT 18 | //======================================================================== 19 | #include "cppt_ag.hpp" 20 | 21 | //======================================================================== 22 | // Helper objects 23 | //======================================================================== 24 | struct A { 25 | virtual void foo(int a) {} 26 | virtual void fez() {} 27 | void bar() {} 28 | }; 29 | 30 | #ifndef COMPILATION_ERROR 31 | struct B : A 32 | #else 33 | struct B final : A 34 | #endif 35 | { 36 | void fez() final{}; 37 | #ifndef COMPILATION_ERROR 38 | void foo(int a) override {} 39 | #else 40 | void foo(float a) override {} 41 | void bar() override {} 42 | #endif 43 | }; 44 | 45 | struct C : B { 46 | void foo() {} 47 | #ifdef COMPILATION_ERROR 48 | void fez() {} 49 | #endif 50 | }; 51 | 52 | //======================================================================== 53 | // main 54 | //======================================================================== 55 | int main() { B var_b; } 56 | -------------------------------------------------------------------------------- /src/pointers_main.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // pointers_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Content: 10 | // - allocating and deallocating dynamic memory for various data structures 11 | // - matching various variants of `new` and `delete` 12 | // - arrays decaying to pointers in function calls 13 | // 14 | // Key takeaway: 15 | // - Every allocation needs to be followed by a deallocation 16 | // - Lack of deallocation leads to memory leaks 17 | // - Mismatch in new and delete leads to memory leaks 18 | // 19 | // Experiment by: 20 | // - (un-)defining MEMORY_LEAK, re-building and re-running 21 | // 22 | // License: MIT 23 | //============================================================================== 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | //============================================================================== 31 | // Data types 32 | //============================================================================== 33 | struct POD { 34 | int iVal; 35 | double dVal; 36 | }; 37 | 38 | // What size do you think this function will report? 39 | void foo(std::string input_array[5]) { 40 | std::cout << "Size of the input array inside foo: " << sizeof input_array 41 | << std::endl; 42 | } 43 | 44 | // Dummy class 45 | class Z { 46 | int a = 10; 47 | }; 48 | 49 | //============================================================================== 50 | // main 51 | //============================================================================== 52 | int main(int argc, char *argv[]) { 53 | cppt::header(argv[0]); 54 | 55 | //------------------------------------------------------------------------- 56 | // 1. BASIC "NEW" 57 | //------------------------------------------------------------------------- 58 | int *ip = new int; 59 | delete ip; 60 | 61 | auto *str = new std::string; 62 | delete str; 63 | 64 | //------------------------------------------------------------------------- 65 | // 2. "NEW" FOR ARRAYS (_WITHOUT_ INTIALISATION) 66 | //------------------------------------------------------------------------- 67 | int *intarr = new int[20]; 68 | delete[] intarr; 69 | 70 | auto *stringarr = new std::string[10]; 71 | delete[] stringarr; 72 | 73 | //------------------------------------------------------------------------- 74 | // 3. "NEW" FOR ARRAYS (_WITH_ INTIALISATION) 75 | //------------------------------------------------------------------------- 76 | // A pointer to 5 0-intialised PODs 77 | POD *pp = new POD[5](); 78 | delete[] pp; 79 | 80 | // A pointer to 9 0-intialised doubles 81 | auto *pd = new double[9](); 82 | delete[] pd; 83 | 84 | //------------------------------------------------------------------------- 85 | // 4. ALLOCATING A DYNAMIC ARRAY OF POINTERs 86 | //------------------------------------------------------------------------- 87 | auto **sp = new std::string *[5]; 88 | for (size_t idx = 0; idx != 5; ++idx) { 89 | sp[idx] = new std::string; 90 | } 91 | 92 | std::cout << "Size of a pointer: " << sizeof sp << std::endl; 93 | #ifdef MEMORY_LEAK 94 | // This won't deallocate memory pointed to by pointers inside sp 95 | delete[] sp; 96 | #else 97 | for (size_t idx = 0; idx != 5; ++idx) { 98 | delete sp[idx]; 99 | } 100 | delete[] sp; 101 | #endif 102 | 103 | //------------------------------------------------------------------------- 104 | // 5. STACK-ALLOCATED ARRAY (size outside and inside a function) 105 | //------------------------------------------------------------------------- 106 | std::string static_stringarr[10]; 107 | std::cout << "Size of static_stringarr: " << sizeof static_stringarr << std::endl; 108 | 109 | foo(static_stringarr); 110 | 111 | //------------------------------------------------------------------------- 112 | // 6. PLACEMENT NEW 113 | //------------------------------------------------------------------------- 114 | char *memory = new char[sizeof(Z)]; 115 | Z *a = new (memory) Z; 116 | #ifndef MEMORY_LEAK 117 | // Placement new requires placement delete 118 | delete (a, memory); 119 | #endif 120 | 121 | char *memory2 = new char[5 * sizeof(Z)]; 122 | Z *b = new (memory2) Z[5]; 123 | #ifndef MEMORY_LEAK 124 | // Placement new requires placement delete 125 | delete[](b, memory2); 126 | #endif 127 | 128 | cppt::footer(argv[0]); 129 | } 130 | -------------------------------------------------------------------------------- /src/rvalue_vs_lvalue_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // rvalue_vs_lvalue_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // TODO 10 | // 11 | // License: MIT 12 | //======================================================================== 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | //======================================================================== 20 | // Helper functions and data structures 21 | //======================================================================== 22 | class SomeClass { 23 | int a = 0xdeadbeef; 24 | }; 25 | 26 | void foo(SomeClass &arg) { return; } 27 | 28 | void bar(SomeClass &&arg) { return; } 29 | 30 | void baz(const SomeClass &arg) { return; } 31 | 32 | void overloaded(const int &arg) { std::cout << " Pass by lvalue\n"; } 33 | void overloaded(int &&arg) { std::cout << " Pass by rvalue\n"; } 34 | 35 | template 36 | void forwarding(T &&arg) { 37 | std::cout << " Call via std::forward: " << std::endl; 38 | overloaded(std::forward(arg)); 39 | 40 | std::cout << " Call via std::move: " << std::endl; 41 | ; 42 | overloaded(std::move(arg)); 43 | 44 | std::cout << " Call by simple passing: " << std::endl; 45 | overloaded(arg); 46 | } 47 | 48 | //======================================================================== 49 | // main 50 | //======================================================================== 51 | int main() { 52 | // 1. L-VALUE REFERENCES 53 | SomeClass b; 54 | SomeClass &c = b; 55 | assert(&b == &c); 56 | 57 | int x; 58 | int &ref1 = x; // A 59 | #ifdef COMPILATION_ERROR 60 | int &ref2 = 5; // B 61 | #endif 62 | 63 | const int &ref3 = x; // C 64 | const int &ref4 = 5; // D 65 | 66 | // 2. R-VALUE REFERENCES 67 | #ifdef COMPILATION_ERROR 68 | int &&ref5 = x; // E 69 | #endif 70 | int &&ref6 = 5; // F 71 | 72 | #ifdef COMPILATION_ERROR 73 | const int &&ref7 = x; // G 74 | #endif 75 | const int &&ref8 = 5; // H 76 | 77 | // 3. FUNCTION CALLS 78 | SomeClass a; 79 | 80 | foo(a); 81 | #ifdef COMPILATION_ERROR 82 | foo(SomeClass()); 83 | #endif 84 | 85 | #ifdef COMPILATION_ERROR 86 | bar(a); 87 | #endif 88 | bar(std::move(a)); 89 | bar(SomeClass()); 90 | 91 | baz(a); 92 | baz(SomeClass()); 93 | 94 | // 4. STD::MOVE VS STD::FORWARD 95 | std::cout << "Initial caller passes rvalue:\n"; 96 | forwarding(5); 97 | 98 | std::cout << "Initial caller passes lvalue:\n"; 99 | int arg = 5; 100 | forwarding(arg); 101 | } 102 | -------------------------------------------------------------------------------- /src/rvo_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // rvo.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // This file can be used to demonstrate (Named-)Return-Value-Optimisation 10 | // ((N)RVO) and how it's related to the move constructor (i.e. whether and 11 | // when it gets called. In this file RVO is also referred to as copy 12 | // elision. 13 | // 14 | // Note that the rules for RVO (but not NRVO) changed in C++17 in which 15 | // copy elision is guaranteed and doesn't require a move constructor. In 16 | // C++11 and C++14 you require the move constructor for this file to 17 | // compile without errors. 18 | // 19 | // Relevant blog posts: 20 | // * https://jonasdevlieghere.com/guaranteed-copy-elision/ 21 | // * https://blog.tartanllama.xyz/guaranteed-copy-elision/ 22 | // * 23 | // https://source.coveo.com/2018/11/07/interaction-between-move-semantic-and-copy-elision-in-c++/ 24 | // * https://riptutorial.com/cplusplus/example/8222/guaranteed-copy-elision 25 | // 26 | // Relevant compiler flags (GCC and clang): 27 | // * -fno-elide-constructors 28 | // With this flag: 29 | // * there's no copy elision at all in C++11 and C++14 30 | // * there's no copy elision for NRVO (and xvalues) in C++17 (but for RVO 31 | // the copy elision is guaranteed) 32 | // 33 | // License: MIT 34 | //======================================================================== 35 | #include 36 | #include 37 | 38 | #include "cppt_ag.hpp" 39 | 40 | //======================================================================== 41 | // Helper objects 42 | //======================================================================== 43 | class List { 44 | struct Node { 45 | explicit Node(int new_val) : val_(new_val), next_(nullptr) {} 46 | int val_; 47 | Node *next_; 48 | }; 49 | 50 | Node *head_ = nullptr; 51 | 52 | public: 53 | explicit List() { std::cout << " Explicit constructor" << std::endl; }; 54 | List(const List &) { std::cout << " Copy constructor" << std::endl; } 55 | #ifdef COMPILATION_ERROR 56 | // In C++11 and C++14 the move constructor is required when returning objects 57 | // by value from functions. That's because copy elision is optional and 58 | // compilers are free to apply it or not (hence a move constructor might be 59 | // required). In C++17 copy elision is guaranteed and hence you no longer 60 | // need the move constructor for functions returning by value. In summary, 61 | // deleting the move constructor will lead to a compiler error for C++11 and 62 | // C++14 (it's required in foo_rvo and foo_nvro). 63 | List(List &&) = delete; 64 | #else 65 | List(List &&) { std::cout << " Move constructor" << std::endl; }; 66 | #endif 67 | ~List() { std::cout << " Destructor" << std::endl; } 68 | List operator=(List) = delete; 69 | List operator=(List &&) = delete; 70 | 71 | void createHead(int new_val) { 72 | auto *new_node = new Node(new_val); 73 | head_ = new_node; 74 | } 75 | }; 76 | 77 | //======================================================================== 78 | // Utility functions 79 | //======================================================================== 80 | List foo_nrvo() { 81 | int a = 10; 82 | List local_list; 83 | local_list.createHead(a); 84 | 85 | return local_list; 86 | } 87 | 88 | List foo_rvo() { 89 | // Under C++17 you are guaranteed that this return value is never copy- or 90 | // move-constructed (thanks to guaranteed copy elision), but instead 91 | // constructed where it's actually needed. 92 | return List{}; 93 | } 94 | 95 | #ifndef COMPILATION_ERROR 96 | // foo_xvalue requires move constructor, which is only defined if 97 | // COMPILATION_ERROR is *not* defined 98 | List foo_xvalue() { 99 | int a = 10; 100 | List local_list; 101 | local_list.createHead(a); 102 | 103 | return std::move(local_list); 104 | } 105 | #endif 106 | 107 | //======================================================================== 108 | // main 109 | //======================================================================== 110 | int main() { 111 | std::cout << "Returning lvalue - NRVO:" << std::endl; 112 | List temp_list_1 = foo_nrvo(); 113 | 114 | std::cout << "Returning prvalue - RVO:" << std::endl; 115 | List temp_list_2 = foo_rvo(); 116 | 117 | #ifndef COMPILATION_ERROR 118 | // foo_xvalue requires move constructor, which is only defined if 119 | // COMPILATION_ERROR is *not* defined 120 | std::cout << "Returning xvalue - no (N)RVO:" << std::endl; 121 | List temp_list_3 = foo_xvalue(); 122 | #endif 123 | 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /src/set_new_handler_example.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // set_new_handler_example.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Illustrates how to use set_new_handler() 10 | // 11 | // DISCLAIMER: 12 | // I haven't been able to demonstrate outofMemory in action. My laptop hangs 13 | // before the exception is thrown. 14 | // 15 | // License: MIT 16 | //======================================================================== 17 | #include 18 | #include 19 | 20 | void outOfMemory() { 21 | std::cout << "Memory exhausted. Program terminates." << std::endl; 22 | exit(1); 23 | } 24 | 25 | class Z { 26 | int a = 10; 27 | }; 28 | 29 | int main() { 30 | std::set_new_handler(outOfMemory); 31 | 32 | char* memory = new char[sizeof(Z)]; 33 | Z* a = new (memory) Z; 34 | 35 | char* memory2 = new char[5 * sizeof(Z)]; 36 | Z* b = new (memory2) Z[5]; 37 | 38 | char* memory3 = new char[4 * sizeof(Z)]; 39 | Z* c = new (memory3) Z[500]; 40 | } 41 | -------------------------------------------------------------------------------- /src/smart_pointers_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // smart_pointers_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Examples of how to use std::unique_ptr, std::shared_ptr and 10 | // std::weak_ptr. Build and run this example and analyse the output. Make 11 | // sure that you understand where it comes from. 12 | // 13 | // The version guarded with: 14 | // * MEMORY_LEAK exhibits memory leaks 15 | // * COMPILER_ERROR triggers a compiler error 16 | // Make sure you that understand why! 17 | // 18 | // License: MIT 19 | //======================================================================== 20 | #include 21 | #include 22 | #include 23 | 24 | #include "cppt_ag.hpp" 25 | 26 | //======================================================================== 27 | // Data structures 28 | //======================================================================== 29 | class A { 30 | public: 31 | explicit A(const std::string &name) : m_name(name) { 32 | m_data = new int[10]; 33 | std::cout << "Constructor for " << m_name << std::endl; 34 | } 35 | ~A() { 36 | std::cout << "Destructor for " << m_name << std::endl; 37 | delete[] m_data; 38 | } 39 | 40 | private: 41 | std::string m_name; 42 | int *m_data; 43 | }; 44 | 45 | struct Player { 46 | #ifdef MEMORY_LEAK 47 | std::shared_ptr partner; 48 | #else 49 | std::weak_ptr partner; 50 | #endif 51 | 52 | ~Player() { std::cout << "~Player\n"; } 53 | }; 54 | 55 | //======================================================================== 56 | // main 57 | //======================================================================== 58 | int main() { 59 | size_t sec_num = {1}; 60 | 61 | // 1. RAW vs SMART POINTER 62 | std::cout << "==> " << sec_num++ << " RAW vs SMART POINTER:" << std::endl; 63 | A *obj_a = new A("Andrzej"); 64 | std::unique_ptr obj_b(new A("Jack")); 65 | 66 | A *obj_a_copy = obj_a; 67 | #ifdef COMPILATION_ERROR 68 | std::unique_ptr obj_b_copy = obj_b; 69 | #endif 70 | 71 | delete obj_a; 72 | 73 | // 2. SHARED_PTR vs WEAK_PTR 74 | std::cout << "==> " << sec_num++ << " SHARED_PTR vs WEAK_PTR:" << std::endl; 75 | std::shared_ptr obj_c(new A("David")); 76 | std::shared_ptr obj_c_copy(obj_c); 77 | 78 | std::cout << "Use count for David: " << obj_c.use_count() << std::endl; 79 | 80 | std::weak_ptr obj_c_weak_ptr(obj_c); 81 | std::cout << "Use count for David: " << obj_c.use_count() << std::endl; 82 | 83 | // 3. CIRCULAR DEPENDENCY 84 | std::cout << "==> " << sec_num++ << " CIRCULAR DEPENDENCY" << std::endl; 85 | std::shared_ptr andrzej(new Player()); 86 | std::shared_ptr jack(new Player()); 87 | 88 | jack->partner = andrzej; 89 | andrzej->partner = jack; 90 | 91 | // 4. MAKE_UNIQUE AND MAKE_SHARED 92 | std::cout << "==> " << sec_num++ << " STD::MAKE_UNIQUE" << std::endl; 93 | #if __cplusplus >= 201403L 94 | std::unique_ptr warzynski = std::make_unique(); 95 | #endif 96 | std::shared_ptr moulson = std::make_shared(); 97 | } 98 | -------------------------------------------------------------------------------- /src/static_assert_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // static_assert_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Demonstrates static_assert and type traits. 10 | // 11 | // Experiment by: 12 | // * (un-)defining COMPILATION_ERROR 13 | // and checking the behaviour before and after. 14 | // 15 | // 16 | // License: MIT 17 | //======================================================================== 18 | #include 19 | #include 20 | 21 | #include "cppt_ag.hpp" 22 | 23 | //======================================================================== 24 | // Helper objects/functions 25 | //======================================================================== 26 | template 27 | class A { 28 | static_assert(N > 10, "Size is too small"); 29 | 30 | T _points[N]; 31 | }; 32 | 33 | template 34 | auto add(T1 t1, T2 t2) -> decltype(t1 + t2) { 35 | static_assert(std::is_integral::value, "Type T1 must be integral"); 36 | static_assert(std::is_integral::value, "Type T2 must be integral"); 37 | 38 | return t1 + t2; 39 | } 40 | 41 | //======================================================================== 42 | // main 43 | //======================================================================== 44 | int main() { 45 | // 1. ASSERT FOR COMPILE-TIME VARS 46 | constexpr int x = 0; 47 | constexpr int y = 1; 48 | static_assert(x != y, "x == y"); 49 | #ifdef COMPILATION_ERROR 50 | static_assert(x == y, "x != y"); 51 | #endif 52 | 53 | // 2. STATIC ASSERT TOGETHER WITH TYPE TRAITS 54 | static_assert(std::is_integral() == true); 55 | static_assert(std::is_integral() == true); 56 | static_assert(std::is_integral() == false); 57 | struct S {}; 58 | static_assert(std::is_integral() == false); 59 | 60 | static_assert( 61 | std::is_same::type, int>::value); 62 | 63 | // 3. STATIC ASSERT IN CUSTOM TEMPLATES 64 | A a1; 65 | #ifdef COMPILATION_ERROR 66 | A a2; 67 | #endif 68 | 69 | std::cout << add(1, 3) << std::endl; 70 | #ifdef COMPILATION_ERROR 71 | std::cout << add(1, 3.14) << std::endl; 72 | std::cout << add("one", 2) << std::endl; 73 | #endif 74 | } 75 | -------------------------------------------------------------------------------- /src/strings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # TARGETS AND THE CORRESPONDING SOURCE FILES 2 | # ============================================== 3 | set(cppTutor_strings_EXECUTABLES 4 | strings_1 5 | strings_2 6 | strings_3 7 | strings_pool 8 | ) 9 | 10 | set(strings_1_SOURCES 11 | ${CMAKE_CURRENT_SOURCE_DIR}/strings_reverse.cpp 12 | ${CMAKE_CURRENT_SOURCE_DIR}/strings_1_main.cpp) 13 | 14 | set(strings_2_SOURCES 15 | ${CMAKE_CURRENT_SOURCE_DIR}/strings_2_main.cpp) 16 | 17 | set(strings_3_SOURCES 18 | ${CMAKE_CURRENT_SOURCE_DIR}/strings_3_main.cpp) 19 | 20 | set(strings_pool_SOURCES 21 | ${CMAKE_CURRENT_SOURCE_DIR}/strings_pool_main.cpp 22 | ${CMAKE_CURRENT_SOURCE_DIR}/strings_pool.cpp) 23 | 24 | # TODO: Turn this into a macro! 25 | foreach( executable ${cppTutor_strings_EXECUTABLES} ) 26 | add_executable( ${executable} 27 | ${${executable}_SOURCES} 28 | ${CMAKE_CURRENT_SOURCE_DIR}/../cppt_tools.cpp 29 | ) 30 | 31 | target_include_directories(${executable} PRIVATE SYSTEM 32 | ${PROJECT_BINARY_DIR}/include/ 33 | ${CMAKE_CURRENT_SOURCE_DIR}/../../include 34 | ) 35 | 36 | target_compile_options(${executable} PRIVATE 37 | "$<$:${cpp-tutor_COMPILER_OPTIONS_CLANG}>" 38 | "$<$:${cpp-tutor_COMPILER_OPTIONS_CLANG}>" 39 | "$<$:${cpp-tutor_COMPILER_OPTIONS_GNU}>" 40 | "$<$:${cpp-tutor_COMPILER_OPTIONS_MSVC}>") 41 | endforeach() 42 | 43 | target_link_libraries(strings_1 44 | LLVMSupport 45 | ) 46 | -------------------------------------------------------------------------------- /src/strings/strings_1_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // strings_1_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Content: 10 | // - char[] vs char* vs std::string vs std::string_view vs llvm::StringRef 11 | // - various ways of reversing strings, depending on representation 12 | // 13 | // It's best to study strings_reverse.cpp alongside this file. 14 | // 15 | // License: MIT 16 | //======================================================================== 17 | #include 18 | #include 19 | 20 | #include "llvm/Support/raw_ostream.h" 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "../include/strings_reverse.hpp" 29 | 30 | int main(int argc, const char** argv) { 31 | cppt::header(argv[0]); 32 | 33 | //------------------------------------------------------------------------- 34 | // 1. DEFINE THE STRINGS 35 | //------------------------------------------------------------------------- 36 | char hello_c[] = "Hello World!"; 37 | // Strictly speaking, `const` is not required here. However, since string 38 | // literals are stored in read-only memory, any attempt to modify it will 39 | // trigger UB. Hence `const char*` is what makes most sense here. 40 | const char* hello_c_c = "Hello World!"; 41 | std::string hello_cpp(hello_c); 42 | std::string_view hello_sv(hello_cpp); 43 | llvm::StringRef hello_sr(hello_cpp); 44 | 45 | //------------------------------------------------------------------------- 46 | // 2. PRINT THE STRINGS 47 | //------------------------------------------------------------------------- 48 | std::cout << "C-string (char[]): " << hello_c << std::endl; 49 | std::cout << "C-string (const char*): " << hello_c_c << std::endl; 50 | std::cout << "C++ string: " << hello_cpp << std::endl; 51 | std::cout << "C++ string_view: " << hello_sv << std::endl; 52 | // As std::cout _does not_ understand llvm::StringRef, you need to grab the 53 | // underlying string for printing. 54 | std::cout << "C++ StringRef (via std::cout): " << hello_sr.str() << std::endl; 55 | // However, llvm::outs() _does_ understand llvm:StringRef. 56 | llvm::outs() << "C++ StringRef (via llvm::outs): " << hello_sr << "\n"; 57 | std::cout << std::endl; 58 | 59 | //------------------------------------------------------------------------- 60 | // 3. PRINT THE SIZES 61 | //------------------------------------------------------------------------- 62 | std::cout << "Size of hello_c: " << sizeof(hello_c) << std::endl; 63 | std::cout << "Size of hello_c_c: " << sizeof(hello_c_c) << std::endl; 64 | std::cout << "Size of hello_cpp: " << sizeof(hello_cpp) << std::endl; 65 | std::cout << "Size of hello_sv: " << sizeof(hello_sv) << std::endl; 66 | std::cout << "Size of hello_sr: " << sizeof(hello_sr) << std::endl; 67 | std::cout << std::endl; 68 | 69 | //------------------------------------------------------------------------- 70 | // 4. REVERSE THE STRINGS 71 | //------------------------------------------------------------------------- 72 | reverse_c_str(hello_c); 73 | std::cout << "Reverse of hello_c: " << hello_c << std::endl; 74 | 75 | #ifdef RUNTIME_ERROR 76 | // Trying to modify read-only memory - UB 77 | reverse_c_str(const_cast(hello_c_c)); 78 | std::cout << "Reverse of hello_c: " << hello_c << std::endl; 79 | #endif 80 | 81 | reverse_cpp_str_swap(&hello_cpp); 82 | std::cout << "Reverse of hello_cpp: " << hello_cpp << std::endl; 83 | 84 | reverse_cpp_str_alg(&hello_cpp); 85 | std::cout << "Reverse of hello_cpp: " << hello_cpp << std::endl; 86 | 87 | #ifdef COMPILATION_ERROR 88 | // string_view is non-modifiable! 89 | reverse_cpp_str_swap(&hello_sv); 90 | reverse_cpp_str_alg(&hello_sv); 91 | std::reverse(hello_sv.begin(), hello_sv.end()); 92 | std::cout << "Reverse of hello_sv: " << hello_sv << std::endl; 93 | #endif 94 | 95 | cppt::footer(argv[0]); 96 | } 97 | -------------------------------------------------------------------------------- /src/strings/strings_2_main.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // strings_2_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Content: 10 | // - SSO, i.e. Short String Optimisation. 11 | // - Implications of copying strings (dynamic allocations) and how 12 | // std::string_view is superior in this respect. 13 | // 14 | // Too fully appreciate these examples, build this file with different 15 | // compilers (e.g. GCC and clang) or different versions of one compiler 16 | // (e.g. Clang 14 vs Clang 20) and compare the output. The size of the 17 | // internal buffer for SSO should differ each time (this should be obvious 18 | // from the printed output). 19 | // 20 | // Key takeaways: 21 | // - Using string_view guarantees that there will be no dynamic 22 | // memory allocations. 23 | // - Most modern implementations of STL use SSO, but the size of the internal 24 | // buffer is not fixed. 25 | // 26 | // License: MIT 27 | //============================================================================== 28 | #include "cppt_ag.hpp" 29 | #include "cppt_tools.hpp" 30 | 31 | #include "llvm/Support/raw_ostream.h" 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | //============================================================================== 40 | // Helper functions 41 | //============================================================================== 42 | // Global replacements for `new` and `delete`. Normally you wouldn't overload 43 | // these without very compelling reasons. Here we just want to track every 44 | // implicit invocation. Otherwise these are rather basic replacements. 45 | void* operator new(std::size_t count) { 46 | std::cout << "[new] " << count << " bytes" << std::endl; 47 | return std::malloc(count); 48 | } 49 | 50 | void operator delete(void* raw_memory) noexcept { 51 | if (nullptr == raw_memory) { 52 | return; 53 | } 54 | 55 | std::cout << "[delete] " 56 | << "addr: " << raw_memory << std::endl; 57 | std::free(raw_memory); 58 | } 59 | 60 | // Dummy functions that take strings. In neither case there should be any 61 | // copying involved, right? 62 | void getString(const std::string& str) {} 63 | void getStringView(std::string_view sv) {} 64 | void getStringRef(llvm::StringRef sv) {} 65 | 66 | //============================================================================== 67 | // main 68 | //============================================================================== 69 | int main(int argc, const char** argv) { 70 | cppt::header(argv[0]); 71 | 72 | size_t sec_num = {1}; 73 | const char message[] = "0123456789-123456789-123456789-123456789"; 74 | 75 | //------------------------------------------------------------------------- 76 | // 1. DEFINE SOME STRINGS 77 | //------------------------------------------------------------------------- 78 | // Are new and delete being invoked? When and why? 79 | std::cout << sec_num++ << ". COPY ASSIGNMENT:\n"; 80 | 81 | std::string small = {"0123456789"}; 82 | std::string large = {"0123456789abcdefghijklmnopqrs"}; 83 | std::string very_large = { 84 | "0123456789ABCDEFGHIKLMNOPQRSabcdefghijklmnopqrs0123456789ABCDEFGHIKLMNOP" 85 | "QRSabcdefghijklmnopqrs"}; 86 | 87 | std::cout << "\n" << sec_num++ << ". EXPLICIT CONSTRUCTOR CALL:\n"; 88 | 89 | std::string five_chars("AAAAA"); 90 | std::string fifteen_chars(15, 'B'); 91 | std::string sixteen_chars(16, 'C'); 92 | std::string one_hundred_chars(100, 'D'); 93 | 94 | //------------------------------------------------------------------------- 95 | // 2. CREATE SUBSTRINGS + string_view + StringRef 96 | //------------------------------------------------------------------------- 97 | // Are new and delete being invoked? When and why? 98 | std::cout << "\n" << sec_num++ << ". SUBSTRINGS + STD::STRING_VIEW:\n"; 99 | 100 | std::string small_substr = small.substr(5); 101 | std::string large_substr = large.substr(15); 102 | std::string very_large_substr = very_large.substr(15); 103 | 104 | std::string_view small_sv(small); 105 | std::string_view large_sv(large); 106 | std::string_view very_large_sv(very_large); 107 | 108 | llvm::StringRef small_sr(small); 109 | llvm::StringRef large_sr(large); 110 | llvm::StringRef very_large_sr(very_large); 111 | 112 | //------------------------------------------------------------------------- 113 | // 3. CALL getString 114 | //------------------------------------------------------------------------- 115 | // Are new and delete being invoked? When and why? 116 | std::cout << "\n" << sec_num++ << ". CALL getString:\n"; 117 | 118 | getString(large); 119 | getString("0123456789-123456789-123456789-123456789"); 120 | getString(message); 121 | 122 | //------------------------------------------------------------------------- 123 | // 4. CALL getStringView 124 | //------------------------------------------------------------------------- 125 | // Are new and delete being invoked? When and why? 126 | std::cout << "\n" << sec_num++ << ". CALL getStringView:\n"; 127 | 128 | getStringView(large); 129 | getStringView("0123456789-123456789-123456789-123456789"); 130 | getStringView(message); 131 | 132 | //------------------------------------------------------------------------- 133 | // 5. CALL getStringVRef 134 | //------------------------------------------------------------------------- 135 | // Are new and delete being invoked? When and why? 136 | std::cout << "\n" << sec_num++ << ". CALL getStringView:\n"; 137 | 138 | getStringView(large); 139 | getStringView("0123456789-123456789-123456789-123456789"); 140 | getStringView(message); 141 | 142 | //------------------------------------------------------------------------- 143 | // 6. DEALLOCATION 144 | //------------------------------------------------------------------------- 145 | std::cout << "\n" << sec_num << ". DEALLOCATE AUTOMATIC VARIABLES:\n"; 146 | std::cout << "(after the closing `{`)\n"; 147 | 148 | cppt::footer(argv[0]); 149 | 150 | // Overloaded operator delete() will be called after closing 151 | // `{`. 152 | } 153 | -------------------------------------------------------------------------------- /src/strings/strings_3_main.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // strings_3_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Content: 10 | // - Performance comparison of std::string vs std::string_view vs 11 | // llvm::StringRef. 12 | // 13 | // This experiment uses the substr method of std::string, 14 | // std::string_view and llvm::StringRef to investigate whether one is faster 15 | // than the other. 16 | // 17 | // Make sure to build this example with different compiler optimisations 18 | // (e.g. -O0 vs -O3). This can be achieved by playing with the 19 | // CMAKE_BUILD_TYPE CMake variable (switch between Debug and Release), or by 20 | // invoking the compiler directly (with -O0 for no optimisations and -O3 for 21 | // very optimised code). 22 | // 23 | // Key takeaway: 24 | // - Because std::string_view and llvm::StringRef never trigger dynamic 25 | // allocations, performance-wise these are far superior when compared to 26 | // plain std::string 27 | // 28 | // 29 | // License: MIT 30 | //============================================================================== 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "cppt_ag.hpp" 43 | #include "cppt_tools.hpp" 44 | #include "llvm/Support/raw_ostream.h" 45 | 46 | //============================================================================== 47 | // Config for the experiment 48 | //============================================================================== 49 | static const int k_substr_len = 30; 50 | static const int k_num_accesses = 1e7; 51 | 52 | //============================================================================== 53 | // main 54 | //============================================================================== 55 | int main(int argc, const char** argv) { 56 | cppt::header(argv[0]); 57 | 58 | //------------------------------------------------------------------------- 59 | // SET-UP 60 | //------------------------------------------------------------------------- 61 | // Read the sample text file (it's _very_ long) 62 | std::ifstream in_file("../src/test.txt"); 63 | std::stringstream str_stream; 64 | str_stream << in_file.rdbuf(); 65 | 66 | // Generate a std::string and std::string_view with the contents of the 67 | // text file 68 | std::string text = str_stream.str(); 69 | size_t size = text.size(); 70 | std::string_view text_sv{text.c_str(), size}; 71 | llvm::StringRef text_sr(text); 72 | 73 | std::cout << "Number of characters: " << size << std::endl; 74 | std::cout << std::endl; 75 | 76 | // Generate random initial position for the substrings 77 | std::random_device seed; 78 | std::mt19937 engine(seed()); 79 | std::uniform_int_distribution<> uniform_dist(0, size - k_substr_len - 2); 80 | 81 | std::vector random_positions; 82 | random_positions.resize(k_num_accesses); 83 | for (auto i = 0; i < k_num_accesses; ++i) { 84 | random_positions.push_back(uniform_dist(engine)); 85 | } 86 | 87 | //------------------------------------------------------------------------- 88 | // BENCHMARKING 89 | //------------------------------------------------------------------------- 90 | // First: std::string 91 | auto start = std::chrono::steady_clock::now(); 92 | for (auto i = 0; i < k_num_accesses; ++i) { 93 | text.substr(random_positions[i], k_substr_len); 94 | } 95 | std::chrono::duration duration_str = 96 | std::chrono::steady_clock::now() - start; 97 | 98 | // Second: std::string_view 99 | start = std::chrono::steady_clock::now(); 100 | for (auto i = 0; i < k_num_accesses; ++i) { 101 | text_sv.substr(random_positions[i], k_substr_len); 102 | } 103 | std::chrono::duration duration_sv = 104 | std::chrono::steady_clock::now() - start; 105 | 106 | // Third: llvm::StringRef 107 | start = std::chrono::steady_clock::now(); 108 | for (auto i = 0; i < k_num_accesses; ++i) { 109 | text_sr.substr(random_positions[i], k_substr_len); 110 | } 111 | std::chrono::duration duration_sr = 112 | std::chrono::steady_clock::now() - start; 113 | 114 | //------------------------------------------------------------------------- 115 | // PRINT RESULTS 116 | //------------------------------------------------------------------------- 117 | std::cout << "Average access times: " << std::endl; 118 | std::cout << "std::string::substr: " << duration_str.count() 119 | << " seconds" << std::endl; 120 | std::cout << "std::string_view::substr: " << duration_sv.count() << " seconds" 121 | << std::endl; 122 | std::cout << "llvm::StringRef::substr: " << duration_sr.count() << " seconds" 123 | << std::endl; 124 | 125 | std::cout << std::endl; 126 | 127 | std::cout << "std::string/std::string_view " 128 | << duration_str.count() / duration_sv.count() << std::endl; 129 | 130 | std::cout << "std::string_view/llvm::StringRef " 131 | << duration_sv.count() / duration_sr.count() << std::endl; 132 | 133 | cppt::footer(argv[0]); 134 | } 135 | -------------------------------------------------------------------------------- /src/strings/strings_pool.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // strings_pool.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Implementation of the StringsPool class. 10 | // 11 | // License: MIT 12 | //============================================================================== 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | StringsPool::StringsPool(char const *const *c_strings, size_t num_strings) 20 | : capacity_(1), 21 | memory_(static_cast(operator new(sizeof(std::string)))) { 22 | reserve(); 23 | for (size_t ii = 0; ii < num_strings; ii++) { 24 | std::string str(c_strings[ii]); 25 | append(str); 26 | } 27 | } 28 | 29 | StringsPool::StringsPool() 30 | : capacity_(1), 31 | memory_(static_cast(operator new(sizeof(std::string)))) {} 32 | 33 | StringsPool::~StringsPool() { 34 | #ifndef MEMORY_LEAK 35 | operator delete(memory_); 36 | #else 37 | // memory_ may look like an array, but it is _not_an_array_! 38 | delete[] memory_; 39 | #endif 40 | } 41 | 42 | void StringsPool::reserve(size_t request) { 43 | while (request > capacity_) { 44 | capacity_ *= 2; 45 | } 46 | 47 | reserve(); 48 | } 49 | 50 | void StringsPool::reserve() { 51 | std::string *new_memory = 52 | static_cast(operator new(capacity_ * sizeof(std::string))); 53 | 54 | for (size_t idx = 0; idx != size_; ++idx) { 55 | new (new_memory + idx) std::string(memory_[idx]); 56 | } 57 | 58 | destroy(); 59 | memory_ = new_memory; 60 | } 61 | 62 | void StringsPool::append(std::string const &new_str) { 63 | reserve(size_ + 1); 64 | new (memory_ + size_) std::string{new_str}; 65 | ++size_; 66 | } 67 | 68 | void StringsPool::destroy() { 69 | using std::string; 70 | for (std::string *sp = memory_ + size_; sp-- != memory_;) { 71 | sp->~string(); 72 | } 73 | 74 | operator delete(memory_); 75 | } 76 | -------------------------------------------------------------------------------- /src/strings/strings_pool_main.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // strings_pool_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Content: 10 | // - How to use StringPool - a custom memory pool implemented with 11 | // placement new 12 | // 13 | // Experiment by: 14 | // - running string_pool with an arbitrary number of (relatively) 15 | // short arguments: 16 | // $ ./string_pool abcd efg hijk 17 | // - (un-)defining MEMORY_LEAK, re-building and re-running 18 | // 19 | // It's best to study strings_pool.cpp alongside this file. 20 | // 21 | // License: MIT 22 | //============================================================================== 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | //============================================================================== 29 | // Utility functions 30 | //============================================================================== 31 | // Prints the strings inside the input instance of StringsPool 32 | void print_strings(StringsPool const &store) { 33 | for (size_t idx = 0; idx != store.size(); ++idx) { 34 | std::cout << store.at(idx) << std::endl; 35 | } 36 | } 37 | 38 | // Creates an instance of StringsPool on stack and prints it's contents (based 39 | // on input args) 40 | void create_stack_object(char *argv[], size_t argc) { 41 | // stack allocated object 42 | StringsPool store{argv, argc}; 43 | print_strings(store); 44 | } 45 | 46 | // Creates and returns heap allocated instance of StringsPool (based on input 47 | // args) 48 | StringsPool *get_heap_object(char *argv[], size_t argc) { 49 | return new StringsPool{argv, argc}; 50 | } 51 | 52 | //============================================================================== 53 | // main 54 | //============================================================================== 55 | int main(int argc, char *argv[]) { 56 | cppt::header(argv[0]); 57 | 58 | // Stack object 59 | create_stack_object(argv, argc); 60 | 61 | // Heap object 62 | StringsPool *sp = get_heap_object(argv, argc); 63 | delete sp; 64 | 65 | // Heap object allocated with placement new (this is seperate from placement 66 | // new used internally by StringsPool). Note that this time the destructor 67 | // hast to be called explicitely. 68 | char buffer[sizeof(StringsPool)]; 69 | sp = new (buffer) StringsPool{argv, static_cast(argc)}; 70 | sp->~StringsPool(); 71 | 72 | cppt::footer(argv[0]); 73 | } 74 | -------------------------------------------------------------------------------- /src/strings/strings_reverse.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // FILE: 3 | // strings_reverse.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Functions for reversing C and C++ strings - implementations. 10 | // Key takeaway - whenever possible, always use algorithms from STL. 11 | // 12 | // License: MIT 13 | //============================================================================== 14 | #include "strings_reverse.hpp" 15 | 16 | #include 17 | 18 | void reverse_c_str(char *input_str) { 19 | if ('\0' == *input_str) { 20 | return; 21 | } 22 | 23 | char tmp = '\0'; 24 | char *str_end = input_str; 25 | char *str_start = input_str; 26 | 27 | while ('\0' != *str_end) { 28 | str_end++; 29 | } 30 | str_end--; 31 | 32 | while (str_end > str_start) { 33 | tmp = *str_start; 34 | *str_start++ = *str_end; 35 | *str_end-- = tmp; 36 | } 37 | } 38 | 39 | void reverse_cpp_str_swap(std::string *input_str) { 40 | size_t length = input_str->length(); 41 | 42 | // Swap characters starting from two end points. 43 | for (size_t ii = 0; ii < length / 2; ii++) { 44 | std::swap((*input_str)[ii], (*input_str)[length - ii - 1]); 45 | } 46 | } 47 | 48 | void reverse_cpp_str_alg(std::string *input_str) { 49 | std::reverse(input_str->begin(), input_str->end()); 50 | } 51 | -------------------------------------------------------------------------------- /src/type_casting_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // type_casting_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Various types of type casting: 10 | // * implicit and explicit (C-style) 11 | // * static_cast 12 | // * dynamic_cast 13 | // * reinterpret_cast 14 | // * const_cast 15 | // The examples presented here demonstrate both _good_ and _bad_ casting 16 | // (always clearly commented). It goes without saying that _bad_ casting 17 | // should be avoided - only because it can be done doesn't mean that it 18 | // should. 19 | // 20 | // Experiment by: 21 | // * (un-)defining RUNTIME_ERROR 22 | // 23 | // Relevant links: 24 | // * https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.cbclx01/implicit_conversion_sequences.htm 25 | // * http://www.cplusplus.com/doc/tutorial/typecasting/ 26 | // * https://embeddedartistry.com/blog/2017/2/28/c-casting-or-oh-no-we-broke-malloc 27 | // 28 | // License: MIT 29 | //======================================================================== 30 | #include 31 | #include 32 | #include 33 | 34 | #include "cppt_ag.hpp" 35 | 36 | //======================================================================== 37 | // Data structures 38 | //======================================================================== 39 | //------------------------------------------------------------------------ 40 | // A and B are related through B's constructors 41 | //------------------------------------------------------------------------ 42 | struct A { 43 | A() { std::cout << " A()" << std::endl; } 44 | }; 45 | 46 | struct B { 47 | public: 48 | B(A a) { std::cout << " B(A)" << std::endl; } 49 | }; 50 | 51 | //------------------------------------------------------------------------ 52 | // C is unrelated to A and B 53 | //------------------------------------------------------------------------ 54 | struct C { 55 | C(int32_t a, int32_t b) : x(a), y(b) { 56 | std::cout << " C(a, b)" << std::endl; 57 | } 58 | int result() { return x + y; } 59 | 60 | private: 61 | int32_t x = 0; 62 | int32_t y = 0; 63 | }; 64 | 65 | //------------------------------------------------------------------------ 66 | // E inherits from D (note that it contains data not present in D) 67 | //------------------------------------------------------------------------ 68 | struct D { 69 | D() { std::cout << " D()" << std::endl; } 70 | virtual ~D() = default; 71 | }; 72 | 73 | struct E : public D { 74 | E() { std::cout << " E()" << std::endl; } 75 | void print() { 76 | std::cout << " Hello from an instance of E!" << std::endl; 77 | std::cout << " The value of some_int: " << some_int << std::endl; 78 | } 79 | 80 | int32_t some_int = 123; 81 | }; 82 | 83 | //======================================================================== 84 | // main 85 | //======================================================================== 86 | int main() { 87 | // 1. ===> IMPLICIT CONVERSION <=== 88 | std::cout << "1. IMPLICIT CONVERSION:" << std::endl; 89 | 90 | // 1.1 Cast between different integer types 91 | int16_t var_a = 2000; 92 | int32_t var_b = var_a; 93 | 94 | // 1.2 Implicit conversion through a specialised constructor 95 | A obj_A; 96 | B obj_B = obj_A; 97 | 98 | std::cout << " Size and type of obj_A: " << sizeof obj_A << " " 99 | << typeid(obj_A).name() << std::endl; 100 | std::cout << " Size and type of obj_B: " << sizeof obj_B << " " 101 | << typeid(obj_B).name() << std::endl; 102 | 103 | // 2. ===> EXPLICIT CONVERSION <=== 104 | std::cout << "2. EXPLICIT CONVERSION:" << std::endl; 105 | 106 | // 2.1 C-STYLE CASTS 107 | std::cout << " 2.1 C-style cast:" << std::endl; 108 | 109 | // 2.1.1 Cast between 16 and 32 bit integer types (good example) 110 | int32_t var_c = (int32_t)var_a; 111 | 112 | // 2.1.2 Cast between pointers (bad example - only because you can, doesn't 113 | // mean you should!) 114 | C* p_obj_C; 115 | p_obj_C = (C*)&obj_B; 116 | #ifdef RUNTIME_ERROR 117 | // p_obj_C is pointing to something that's not an instance of C! 118 | std::cout << " The result from p_obj_C->result(): " << p_obj_C->result() 119 | << std::endl; 120 | #endif 121 | 122 | // 2.2 DYNAMIC_CAST 123 | // Used for conversion of polymorphic types. Does runtime check of the types. 124 | // Returns nullptr if the conversion is not possible (so remember to check 125 | // the output!) 126 | std::cout << " 2.2 dynamic_cast:" << std::endl; 127 | 128 | D obj_D; 129 | E obj_E; 130 | D* p_obj_D; 131 | E* p_obj_E; 132 | 133 | // 2.2.1 Cast derived to base (fine) 134 | p_obj_D = dynamic_cast(&obj_E); 135 | if (p_obj_D == nullptr) { 136 | std::cout << " Null pointer on 1st dynamic_cast" << std::endl; 137 | } 138 | 139 | // 2.2.2 Cast base to derived (bad) 140 | p_obj_E = dynamic_cast(&obj_D); 141 | if (p_obj_E == nullptr) { 142 | std::cout << " Null pointer on 2nd dynamic_cast" << std::endl; 143 | } 144 | 145 | // 2.3 STATIC_CAST 146 | // Used for conversion of non-polymorphic types. Doesn't do runtime checks. 147 | std::cout << " 2.3 static_cast:" << std::endl; 148 | 149 | // 2.3.1 Cast between numeric types (good) 150 | double var_d = 3.14159265; 151 | int32_t var_i = static_cast(var_d); 152 | std::cout << " The value of var_i: " << var_i << std::endl; 153 | 154 | // 2.3.2 Cast base to derived (bad) 155 | p_obj_E = static_cast(&obj_D); 156 | #ifdef RUNTIME_ERROR 157 | // ... p_obj_E points to an instance of D and E::print() accesses a 158 | // variable that's not present in D (the call itself will probably work 159 | // fine). 160 | p_obj_E->print(); 161 | #endif 162 | 163 | // 2.4 REINTERPRET_CAST 164 | // Allows any pointer to be converted into any other pointer type. Also allows 165 | // any integral type to be converted into any pointer type and vice versa. No 166 | // runtime checks are performed. 167 | std::cout << " 2.4 reinterpret_cast:" << std::endl; 168 | 169 | // 2.4.1 Cast one pointer type to another pointer type - bad, bad, bad! 170 | A* p_obj_A = &obj_A; 171 | p_obj_E = reinterpret_cast(p_obj_A); 172 | #ifdef RUNTIME_ERROR 173 | // p_obj_E points to an instance of A and E::print() accesses a 174 | // variable that's not present in A. 175 | p_obj_E->print(); 176 | #endif 177 | 178 | // 2.4.2 Cast integer to a pointer - bad, bad, bad! 179 | int32_t var_j = 123; 180 | // Bad, bad, bad! 181 | p_obj_A = reinterpret_cast(var_j); 182 | 183 | // 2.5 CONST_CAST 184 | // Used to remove the const, volatile, and __unaligned attributes. 185 | std::cout << " 2.5 const_cast:" << std::endl; 186 | 187 | // 2.5.1 Modify a non-const variable through a reference-to-const (good 188 | // example) 189 | int32_t var_k = 3; 190 | const int32_t& rck = var_k; 191 | const_cast(rck) = 4; 192 | std::cout << " The value of var_k: " << var_k << std::endl; 193 | 194 | // 2.5.2 Modify a const variable through a reference-to-const (bad example) 195 | const int32_t var_l = 3; 196 | const int32_t& rcl = var_l; 197 | #ifdef RUNTIME_ERROR 198 | // var_l and for this reason this UB (though might actually work fine on some 199 | // platforms) 200 | const_cast(rcl) = 4; 201 | std::cout << " The value of var_l: " << var_l << std::endl; 202 | #endif 203 | } 204 | -------------------------------------------------------------------------------- /src/vector_main_1.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // vector_main_1.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // A very simple comparison of std::vector and llvm::SmallVector: 10 | // * Capacity vs size. 11 | // * Creating a large LLVM vector (in terms of capacity and size) on the 12 | // stack. 13 | // * Are `new` + `delete` used for allocation? Only for std::vector. 14 | // * Short presentation of fmt - implementation of the format library. 15 | // 16 | // For more in-depth comparison, see vector_main_2.cpp 17 | // 18 | // License: MIT 19 | //======================================================================== 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #if __cplusplus >= 202000L 28 | // Allows fancy way of printing ranges. Available in C++20. On Ubuntu, you will 29 | // have to install libfmt-dev. 30 | // 31 | // This is an implementation of: 32 | // * https://en.cppreference.com/w/cpp/utility/format 33 | #include 34 | #endif 35 | 36 | //============================================================================== 37 | // Helper functions 38 | //============================================================================== 39 | // Global replacements for `new` and `delete`. Normally you wouldn't overload 40 | // these without very compelling reasons. Here we just want to track every 41 | // implicit invocation. Otherwise these are rather basic replacements. 42 | void* operator new(std::size_t count) { 43 | std::cout << "[new] " << count << " bytes" << std::endl; 44 | return std::malloc(count); 45 | } 46 | 47 | void operator delete(void* raw_memory) noexcept { 48 | if (nullptr == raw_memory) { 49 | return; 50 | } 51 | 52 | std::cout << "[delete] " 53 | << "addr: " << raw_memory << std::endl; 54 | std::free(raw_memory); 55 | } 56 | 57 | auto printVec = [](const auto& vec) { 58 | for (const auto& x : vec) std::cout << x << ' '; 59 | std::cout << '\n'; 60 | }; 61 | 62 | //============================================================================== 63 | // main 64 | //============================================================================== 65 | int main(int argc, const char** argv) { 66 | cppt::header(argv[0]); 67 | 68 | size_t sec_num = 1; 69 | 70 | //------------------------------------------------------------------------- 71 | // 1. DEFINE SOME VECTORS 72 | //------------------------------------------------------------------------- 73 | std::cout << sec_num++ << ". DEFINE VECTORS:" << std::endl; 74 | std::vector v_empty = {}; 75 | std::vector v_small = {123}; 76 | std::vector v_large(2000, 123); 77 | llvm::SmallVector sv_empty = {}; 78 | llvm::SmallVector sv_small = {321}; 79 | llvm::SmallVector sv_large_default_inline(2000, 321); 80 | // Create 2000 elements on stack! 81 | llvm::SmallVector sv_large_all_inline(2000, 321); 82 | 83 | //------------------------------------------------------------------------- 84 | // 2. PRINT THE VECTORS 85 | //------------------------------------------------------------------------- 86 | std::cout << std::endl << sec_num++ << ". PRINT VECTORS:" << std::endl; 87 | std::cout << "C++ Vector: "; 88 | printVec(v_small); 89 | #if __cplusplus >= 202002L 90 | fmt::print("v = {}\n", v_small); 91 | #endif 92 | std::cout << "LLVM SmallVector: "; 93 | printVec(sv_small); 94 | #if __cplusplus >= 202002L 95 | fmt::print("v = {}\n", sv_small); 96 | #endif 97 | 98 | //------------------------------------------------------------------------- 99 | // 3. PRINT THE SIZES 100 | //------------------------------------------------------------------------- 101 | std::cout << std::endl << sec_num++ << ". PRINT VECTOR SIZES:" << std::endl; 102 | 103 | std::cout << "C++ Vector\n"; 104 | std::cout << "Size of v_empty: " << sizeof(v_empty) << " (bytes)" 105 | << std::endl; 106 | std::cout << "Size of v_small: " << sizeof(v_small) << " (bytes)" 107 | << std::endl; 108 | std::cout << "Size of v_large: " << sizeof(v_large) << " (bytes)" 109 | << std::endl; 110 | 111 | std::cout << "LLVM SmallVector\n"; 112 | std::cout << "Size of sv_empty: " << sizeof(sv_empty) << " (bytes)" 113 | << std::endl; 114 | std::cout << "Size of sv_small: " << sizeof(sv_small) << " (bytes)" 115 | << std::endl; 116 | std::cout << "Size of sv_large_default_inline: " 117 | << sizeof(sv_large_default_inline) << " (bytes)" << std::endl; 118 | std::cout << "Size of sv_large_all_inline: " << sizeof(sv_large_all_inline) 119 | << " (bytes)" << std::endl; 120 | 121 | //------------------------------------------------------------------------- 122 | // 3. PRINT THE CAPACITIES 123 | //------------------------------------------------------------------------- 124 | std::cout << std::endl 125 | << sec_num++ << ". PRINT VECTOR CAPACITIES:" << std::endl; 126 | 127 | std::cout << "LLVM SmallVector\n"; 128 | std::cout << "Capacity of sv_small: " << sv_small.capacity_in_bytes() 129 | << " (bytes)" << std::endl; 130 | std::cout << "Capacity of sv_large_all_inline: " 131 | << sv_large_default_inline.capacity_in_bytes() << " (bytes)" 132 | << std::endl; 133 | std::cout << std::endl; 134 | 135 | cppt::footer(argv[0]); 136 | } 137 | -------------------------------------------------------------------------------- /src/vector_main_2.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // vector_main_2.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // This file compares the efficiency of std::vector vs llvm::SmallVector in 10 | // terms of memory allocation. 11 | // 12 | // The default size of llvm::SmallVector is 64 bytes, see: 13 | // * https://github.com/llvm/llvm-project/blob/release/20.x/llvm/include/llvm/ADT/SmallVector.h#L1137 14 | // The default number of inline elements (i.e. not requiring allocation) will 15 | // depend on the width of the underlying element type and is calculated 16 | // here: 17 | // * https://github.com/llvm/llvm-project/blob/release/20.x/llvm/include/llvm/ADT/SmallVector.h#L1172 18 | // 19 | // AFAIK, std::vector, no matter the number of elements (nor the element 20 | // bit-width), will allways require memory allocation (i.e. there no inline 21 | // elements). 22 | // 23 | // The optimization implemented in llvm::SmallVector is also known as Small 24 | // Object Optimiztion. 25 | // 26 | // License: MIT 27 | //======================================================================== 28 | #ifndef WIN32 29 | #include 30 | #endif 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | //============================================================================== 44 | // alloc_counter - helper hooks to count alloc-like invocations 45 | //============================================================================== 46 | namespace alloc_counter { 47 | std::atomic malloc_count{0}; 48 | std::atomic calloc_count{0}; 49 | std::atomic realloc_count{0}; 50 | std::atomic free_count{0}; 51 | 52 | // Use this to lock the counters to make sure we are not counting something 53 | // unrelated to what is being benchmarked. Without this being toggled, we could 54 | // be (for example) be measuring memory allocation for temporary string 55 | // creation when calling `print_summary`. 56 | std::atomic counters_locked = false; 57 | 58 | void reset_counters() { 59 | malloc_count = 0; 60 | calloc_count = 0; 61 | realloc_count = 0; 62 | free_count = 0; 63 | } 64 | 65 | void lock_counters() { counters_locked = true; } 66 | 67 | void unlock_counters() { counters_locked = false; } 68 | 69 | void print_summary(std::string&& calls_of_what) { 70 | static size_t sub_sec_num = 0; 71 | 72 | std::cout << " " << sub_sec_num++ << ". Count of alloc calls (" 73 | << calls_of_what << "):" << std::endl; 74 | std::cout << " " << "malloc : " << malloc_count.load() << std::endl; 75 | std::cout << " " << "calloc : " << calloc_count.load() << std::endl; 76 | std::cout << " " << "realloc : " << realloc_count.load() << std::endl; 77 | std::cout << " " << "free : " << free_count.load() << std::endl; 78 | } 79 | } // namespace alloc_counter 80 | 81 | //============================================================================== 82 | // Override C allocators 83 | //============================================================================== 84 | extern "C" { 85 | 86 | void* malloc(size_t size) { 87 | if (!alloc_counter::counters_locked) alloc_counter::malloc_count++; 88 | 89 | // `malloc` function type 90 | using malloc_fn = void* (*)(size_t); 91 | 92 | // Give me the next definition of `malloc` after this one in the shared 93 | // library resolution chain. This should be the libc `malloc`. 94 | static malloc_fn real_malloc = (malloc_fn)dlsym(RTLD_NEXT, "malloc"); 95 | return real_malloc(size); 96 | } 97 | 98 | void* calloc(size_t nmemb, size_t size) { 99 | if (!alloc_counter::counters_locked) alloc_counter::calloc_count++; 100 | 101 | // `calloc` function type 102 | using calloc_fn = void* (*)(size_t, size_t); 103 | 104 | // Give me the next definition of `calloc` after this one in the shared 105 | // library resolution chain. This should be the libc `malloc`. 106 | static calloc_fn real_calloc = (calloc_fn)dlsym(RTLD_NEXT, "calloc"); 107 | return real_calloc(nmemb, size); 108 | } 109 | 110 | void* realloc(void* ptr, size_t size) { 111 | if (!alloc_counter::counters_locked) alloc_counter::realloc_count++; 112 | 113 | // `realloc` function type 114 | using realloc_fn = void* (*)(void*, size_t); 115 | 116 | // Give me the next definition of `realloc` after this one in the shared 117 | // library resolution chain. This should be the libc `malloc`. 118 | static realloc_fn real_realloc = (realloc_fn)dlsym(RTLD_NEXT, "realloc"); 119 | return real_realloc(ptr, size); 120 | } 121 | 122 | void free(void* ptr) { 123 | if (!alloc_counter::counters_locked) alloc_counter::free_count++; 124 | 125 | // `free` function type 126 | using free_fn = void (*)(void*); 127 | 128 | // Give me the next definition of `free` after this one in the shared library 129 | // resolution chain. This should be the libc `malloc`. 130 | static free_fn real_free = (free_fn)dlsym(RTLD_NEXT, "free"); 131 | real_free(ptr); 132 | } 133 | } 134 | 135 | template 136 | void populate_vector(T vector, size_t N) { 137 | for (auto j = 0; j < N; j++) vector.push_back(j); 138 | } 139 | 140 | //======================================================================== 141 | // main 142 | //======================================================================== 143 | int main(int argc, const char** argv) { 144 | cppt::header(argv[0]); 145 | 146 | size_t sec_num = 1; 147 | // This is the number of inline int32_t elements in llvm::SmallVector. For 148 | // int64_t, this will already trigger an allocation (12 * 8 bytes > 64 bytes). 149 | const size_t k_num_elems = 12; 150 | 151 | //------------------------------------------------------------------------- 152 | // BASIC DEMO 153 | //------------------------------------------------------------------------- 154 | std::cout << sec_num++ << ". BASIC DEMO:" << std::endl; 155 | 156 | alloc_counter::reset_counters(); 157 | alloc_counter::unlock_counters(); 158 | std::vector vec_i32(k_num_elems); 159 | alloc_counter::lock_counters(); 160 | alloc_counter::print_summary("Single std::vector, 0 elements, int32_t"); 161 | 162 | alloc_counter::reset_counters(); 163 | alloc_counter::unlock_counters(); 164 | std::vector vec_i64(k_num_elems); 165 | alloc_counter::lock_counters(); 166 | alloc_counter::print_summary("Single std::vector, 0 elements, int64_t"); 167 | 168 | alloc_counter::reset_counters(); 169 | alloc_counter::unlock_counters(); 170 | llvm::SmallVector small_vec_i32(k_num_elems); 171 | alloc_counter::lock_counters(); 172 | alloc_counter::print_summary("Single llvm::SmallVector, 0 elements, int32_t"); 173 | 174 | alloc_counter::reset_counters(); 175 | alloc_counter::unlock_counters(); 176 | llvm::SmallVector small_vec_i64(k_num_elems); 177 | alloc_counter::lock_counters(); 178 | alloc_counter::print_summary("Single llvm::SmallVector, 0 elements, int64_t"); 179 | 180 | //------------------------------------------------------------------------- 181 | // BENCHMARKING 182 | //------------------------------------------------------------------------- 183 | std::cout << sec_num++ << ". BENCHMARKING:" << std::endl; 184 | std::vector array_v[100]; 185 | llvm::SmallVector array_sv[100]; 186 | 187 | // Array of 10 std::vector 188 | alloc_counter::reset_counters(); 189 | auto start = std::chrono::steady_clock::now(); 190 | alloc_counter::unlock_counters(); 191 | for (auto i = 0; i < 10; i++) { 192 | array_v[i] = std::vector(0); 193 | populate_vector(array_v[i], k_num_elems); 194 | } 195 | std::chrono::duration duration_v_10 = 196 | std::chrono::steady_clock::now() - start; 197 | alloc_counter::lock_counters(); 198 | alloc_counter::print_summary("Array of 10 std::vector"); 199 | 200 | // Array of 100 std::vector 201 | alloc_counter::reset_counters(); 202 | start = std::chrono::steady_clock::now(); 203 | alloc_counter::unlock_counters(); 204 | for (auto i = 0; i < 100; i++) { 205 | array_v[i] = std::vector(0); 206 | populate_vector(array_v[i], k_num_elems); 207 | } 208 | std::chrono::duration duration_v_100 = 209 | std::chrono::steady_clock::now() - start; 210 | alloc_counter::lock_counters(); 211 | alloc_counter::print_summary("Array of 100 std::vector"); 212 | 213 | // Array of 10 SmallVector 214 | alloc_counter::reset_counters(); 215 | start = std::chrono::steady_clock::now(); 216 | alloc_counter::unlock_counters(); 217 | for (auto i = 0; i < 10; i++) { 218 | array_sv[i] = llvm::SmallVector(0); 219 | populate_vector(array_sv[i], k_num_elems); 220 | } 221 | std::chrono::duration duration_sv_10 = 222 | std::chrono::steady_clock::now() - start; 223 | alloc_counter::lock_counters(); 224 | alloc_counter::print_summary("Array of 10 SmallVector"); 225 | 226 | // Array of 100 SmallVector 227 | alloc_counter::reset_counters(); 228 | start = std::chrono::steady_clock::now(); 229 | alloc_counter::unlock_counters(); 230 | for (auto i = 0; i < 100; i++) { 231 | array_sv[i] = llvm::SmallVector(0); 232 | populate_vector(array_sv[i], k_num_elems); 233 | } 234 | std::chrono::duration duration_sv_100 = 235 | std::chrono::steady_clock::now() - start; 236 | alloc_counter::lock_counters(); 237 | alloc_counter::print_summary("Array of 100 SmallVector"); 238 | 239 | //------------------------------------------------------------------------- 240 | // PRINT RESULTS 241 | //------------------------------------------------------------------------- 242 | std::cout << sec_num++ 243 | << ". CUMMULATIVE ALLOCATION + ASSIGNMENT TIMES:" << std::endl; 244 | std::cout << "std::vector (10): " << duration_v_10.count() 245 | << " seconds" << std::endl; 246 | std::cout << "std::vector (100): " << duration_v_100.count() 247 | << " seconds" << std::endl; 248 | std::cout << "llvm::SmallVector (10): " << duration_sv_10.count() 249 | << " seconds" << std::endl; 250 | std::cout << "llvm::SmallVector (100): " << duration_sv_100.count() 251 | << " seconds" << std::endl; 252 | 253 | std::cout << std::endl; 254 | 255 | cppt::footer(argv[0]); 256 | } 257 | -------------------------------------------------------------------------------- /test/cpp_tutor_ut_main.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // test/cpp_tutor_ut_main.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // The main function for unit tests. 10 | // 11 | // License: Apache License 2.0 12 | //======================================================================== 13 | #include 14 | 15 | GTEST_API_ int main(int argc, char *argv[]) { 16 | // Let Google Test parse the arguments first 17 | ::testing::InitGoogleTest(&argc, argv); 18 | 19 | // Then, you can handle any that are left 20 | 21 | return RUN_ALL_TESTS(); 22 | } 23 | -------------------------------------------------------------------------------- /test/tests_strings.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // test/tests_strings.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Unit tests for functions for reversing strings (defined in strings.cpp) 10 | // 11 | // License: Apache License 2.0 12 | //======================================================================== 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | //======================================================================== 21 | // Test Fixtures 22 | //======================================================================== 23 | struct TestReverseStrings : public ::testing::Test { 24 | // A vector of test strings tuples: {original string, reversed string} 25 | std::vector> test_strings; 26 | 27 | TestReverseStrings() { 28 | test_strings = {{{""}, {""}}, 29 | {{"aba"}, {"aba"}}, 30 | {{"abba"}, {"abba"}}, 31 | {{"Andrzej Warzynski"}, {"iksnyzraW jezrdnA"}}, 32 | {{"aaaaaaaaaaaaaaaaaaaa"}, {"aaaaaaaaaaaaaaaaaaaa"}}, 33 | {{"xxxxxxxxxxyyyyyyyyyy"}, {"yyyyyyyyyyxxxxxxxxxx"}}}; 34 | } 35 | 36 | protected: 37 | void SetUp() override {} 38 | void TearDown() override {} 39 | }; 40 | 41 | //======================================================================== 42 | // Tests 43 | //======================================================================== 44 | TEST_F(TestReverseStrings, reverse_c_str) { 45 | for (auto pair : test_strings) { 46 | // What follows is a bit nasty way of generating a modifiable C-string 47 | // (i.e. char*) from a CPP string (i.e. resorting to raw painters and 48 | // dynamic memory allocation). (one gotcha: std::string.length() won't 49 | // include the null character) 50 | auto length = std::get<1>(pair).length(); 51 | char *current_str = nullptr; 52 | if (length > 0) { 53 | current_str = new char[length]; 54 | strncpy(current_str, std::get<0>(pair).c_str(), length + 1); 55 | } else { 56 | current_str = new char[1]; 57 | *current_str = '\0'; 58 | } 59 | 60 | reverse_c_str(current_str); 61 | ASSERT_STREQ(current_str, std::get<1>(pair).c_str()); 62 | 63 | delete[](current_str); 64 | } 65 | } 66 | 67 | TEST_F(TestReverseStrings, reverse_cpp_str_swap) { 68 | for (auto pair : test_strings) { 69 | std::string current_str(std::get<0>(pair)); 70 | 71 | reverse_cpp_str_swap(¤t_str); 72 | ASSERT_EQ(current_str, std::get<1>(pair).c_str()); 73 | } 74 | } 75 | 76 | TEST_F(TestReverseStrings, reverse_cpp_str_alg) { 77 | for (auto pair : test_strings) { 78 | std::string current_str(std::get<0>(pair)); 79 | 80 | reverse_cpp_str_alg(¤t_str); 81 | ASSERT_EQ(current_str, std::get<1>(pair).c_str()); 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /test/tests_strings_pool.cpp: -------------------------------------------------------------------------------- 1 | //======================================================================== 2 | // FILE: 3 | // test/tests_strings_pool.cpp 4 | // 5 | // AUTHOR: 6 | // banach-space@github 7 | // 8 | // DESCRIPTION: 9 | // Unit tests for the StringsPool class (defined in strings_pool.cpp) 10 | // 11 | // License: Apache License 2.0 12 | //======================================================================== 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | //======================================================================== 20 | // Tests 21 | //======================================================================== 22 | TEST(StringsPoolObject, smoke_test) { 23 | StringsPool strs; 24 | 25 | ASSERT_EQ(0, strs.size()); 26 | } 27 | 28 | TEST(StringsPoolObject, insert_elements) { 29 | std::vector test_strings = {"Andrzej", "Warzynski", "Nikita", 30 | "Dell", "", "A"}; 31 | 32 | StringsPool strs{}; 33 | for (auto str : test_strings) { 34 | strs.append(str); 35 | } 36 | 37 | ASSERT_EQ(strs.size(), test_strings.size()); 38 | 39 | for (auto ii = 0; ii < strs.size(); ii++) { 40 | ASSERT_EQ(strs.at(ii), test_strings[ii]); 41 | } 42 | } 43 | --------------------------------------------------------------------------------