C++¶

C++ started as "C with Classes" in 1979 and grew into a massive language. It's C's powerful sibling you get C's low level control plus OOP, templates, exceptions, and a huge standard library.
Table of Contents¶
- 1. Getting Started
- 4. Compile and run (CLI + CMake)
- 2. C++ Language Fundamentals
- 3. The Standard Library (STL)
- 4. Object Oriented Programming (OOP)
- 5. Modern C++ Features
- 6. Concurrency and Parallelism
- 7. I/O and Data Handling
- 8. Secure Coding Practices
- 40. Secure C++: Do this, avoid that
- 41. Avoid dangerous functions
- 42. Bounds and input validation
- 43. Integer safety
- 44. Lifetime and ownership
- 45. Undefined behavior and portability
- 46. Filesystem safety
- 47. Cryptography guidance
- 48. Concurrency pitfalls
- 49. Sanitizers and hardening
- 50. Static analysis and formatting
- 9. Build, Test, and Deploy
- 52. Compilers (GCC/Clang/MSVC)
- 10. Advanced Topics & Patterns
- 11. Help and Resources
- Sources
Part 1: Getting Started¶
1. Why C++ (and why modern-C++)¶
C++ runs the world's most performance critical software. Web browsers (Chrome, Firefox), game engines (Unreal, Unity), databases (MySQL, MongoDB), and operating system components all rely on C++ for one reason: when you need speed and control, C++ delivers.
The Evolution:
C++98 was... functional. It worked. But it was verbose, error prone, and had too many ways to shoot yourself in the foot. Modern C++ (C++11 and beyond) is a different language: - Smart pointers eliminate manual memory management - RAII ensures resources are cleaned up automatically - Auto and type deduction reduce verbosity - Ranges make algorithms readable - Concepts provide better error messages - Strong standard library means less custom code
Modern C++ Philosophy:
The old C++ way: "Trust me, I know what I'm doing." The modern way: "Let the language help me avoid mistakes."
Principles for Safe C++: - Prefer values, references, and STL containers over raw pointers - Use smart pointers (std::unique_ptr, std::shared_ptr) instead of new/delete - Avoid raw memory manipulation unless absolutely necessary (and document why) - Enable compiler warnings (-Wall -Wextra -Wpedantic), use sanitizers, and run static analyzers - Write code that's hard to use incorrectly (make interfaces safe by default)
2. What you need to install¶
Pick a compiler and basic tools:
- Linux/macOS: Install GCC or Clang Debian/Ubuntu:
sudo apt get update && sudo apt get install -y build essential clangFedora:sudo dnf install gcc c++clangmacOS:xcode select --install(then install Homebrew and optionallybrew install llvm`) - Windows: Install Visual Studio (Desktop development with C++) or MSYS2/MinGW (pacman -S mingw w64-x86_64-gcc)
- CMake (portable build system): https://cmake.org/download/
- A good editor: VS Code, CLion, Visual Studio, Qt Creator, Vim/Neovim, Emacs
3. Your first program¶
hello.cpp:
#include <iostream>
int main() {
// This is your entry point
std::cout << "Hello, C++!" << '\n'; // Print a friendly greeting
}
Compile and run (Linux/macOS with g++):
# Compile
g++ -std=c++20 -O2 -Wall -Wextra hello.cpp -o hello
# Run
./hello
That Compiler Flag Breakdown: - -std=c++20: Use C++20 standard (or c++17, c++11 depending on what you need) - -O2: Optimization level 2 (faster code, but slower compilation) - -Wall -Wextra: Enable warnings (catch mistakes early)
Windows (MSVC Developer Command Prompt):
cl /std:c++20 /W4 /EHsc hello.cpp
hello.exe
4. Compile and run (CLI +-CMake)¶
Common compiler flags:
- Language standard: -std=c++17 or -std=c++20
- Warnings: -Wall -Wextra -Wpedantic (GCC/Clang) or /W4 /permissive- (MSVC)
- Debug: -g (GCC/Clang) or /Zi (MSVC)
- Optimization: -O2/-O3 (GCC/Clang) or /O2 (MSVC)
- Sanitizers (Clang/GCC): -fsanitize=address,undefined,thread
Minimal CMake project:
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(hello LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(hello src/main.cpp)
# Recommended warnings on GCC/Clang
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
target_compile_options(hello PRIVATE -Wall -Wextra -Wpedantic -Wconversion -Wsign conversion)
endif()
# Recommended warnings on MSVC
if (MSVC)
target_compile_options(hello PRIVATE /W4 /permissive-)
endif()
src/main.cpp
#include <iostream>
int main() {
std::cout << "Hello from CMake!
";
}
Build:
mkdir build && cd build
cmake .. && cmake --build .
5. Project layout¶
A simple, scalable layout:
project/
CMakeLists.txt
cmake/
include/
project/
api.hpp
src/
main.cpp
api.cpp
tests/
CMakeLists.txt
test_api.cpp
third_party/
Part 2: C++ Language Fundamentals¶
6. Comments and structure¶
- Single line comments use
// - Multi line comments use
/* ... */ - Your program starts at
int main() { ... }
#include <iostream>
int main() {
// Print to stdout
std::cout << "Line 1" << '\n';
std::cout << "Line 2" << std::endl; // std::endl flushes the output buffer
}
Style note: Avoid using namespace std;¶
This guide intentionally avoids writing using namespace std; at global scope and instead qualifies standard library names with std::.
Reasons: - Namespace pollution: Pulls hundreds of names into the global namespace, increasing chances of collisions with your own symbols (e.g., size, distance, begin/end, swap) and platform macros (e.g., Windows min/max). - Header hygiene: If placed in a header, it leaks into every translation unit that includes it, causing surprising errors elsewhere. - Overload/ADL pitfalls: Unqualified calls can bind to unintended overloads via argument dependent lookup, leading to subtle bugs. - Clarity and maintainability: std:: makes ownership explicit and improves searchability and code reviews. - Future proofing: As the standard adds more names, full qualification reduces ambiguity.
Prefer these alternatives when brevity helps: - Narrow using declarations in small scopes (function/block), not in headers: using std::string; using std::cout; - Namespace aliases for long names: namespace fs = std::filesystem; - Purpose specific literal namespaces: using namespace std::chrono_literals; or using namespace std::string_literals; in limited scope.
Bad (global directive):
using namespace std; // Avoid
vector<int> v; string s; cout << v.size() << ' ' << s.size() << '\n';
Better (qualified or narrow scope):
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<int> v;
std::string s;
std::cout << v.size() << ' ' << s.size() << '\n';
}
#include <iostream>
#include <string>
using std::cout; // Narrow scope and specific names
using std::string; // Avoid in headers; fine in small .cpp scope
int main() {
string s = "hi";
cout << s.size() << '\n';
}
Note: In this guide, using namespace std::chrono_literals; appears in a small scope to enable time literals like 100ms; this is considered safe and intentional.
7. Types and variables¶
Here are the fundamental types you'll use:
bool,char,wchar_t,char8_t,char16_t,char32_t- Signed/unsigned integers:
short,int,long,long long - Floating point:
float,double,long double size_t(unsigned size/index),ptrdiff_t(signed pointer difference)
#include <cstdint>
int main() {
bool ok = true; // true or false
char c = 'A'; // single character
std::uint32_t u = 42; // 32-bit unsigned integer
std::int64_t big = -1; // 64-bit signed integer
double pi = 3.14159; // double precision float
auto inferred = 10; // compiler infers int
}
Use fixed width types from
7b. Data types: sizes, ranges, buffers, and special cases¶
- The standard only guarantees minimum ranges and relative relationships. Always use sizeof(T) and std::numeric_limits
for portable code. - Common 64-bit ABIs and what they imply for sizes: LP64 (Linux/macOS, GCC/Clang): int=32, long=64, pointer=64 LLP64 (Windows/MSVC): int=32, long=32, long long=64, pointer=64
- Typical sizes (do not hardcode; measure with sizeof): bool: 1 byte (value is 0 or 1 when converted to int) char: 1 byte; signedness is implementation defined (use signed char/unsigned char or std::int8_t/std::uint8_t) wchar_t: 2 bytes on Windows (UTF-16 code unit), 4 bytes on Linux (UTF-32 code unit) char8_t (C++20): 1 byte UTF-8 code unit; char16_t: 2 bytes; char32_t: 4 bytes short: ≥16-bit (commonly 2 bytes) int: ≥16-bit (commonly 4 bytes) long: LP64=8 bytes, LLP64=4 bytes long long: ≥64-bit (commonly 8 bytes) float: 4 bytes double: 8 bytes; long double: 16 bytes on GCC/Clang x86-64 Linux (80-bit extended precision storage), often 8 bytes on MSVC/macOS size_t, ptrdiff_t: same width as pointer (32-bit on 32-bit, 64-bit on 64-bit) Pointers: typically 8 bytes on 64-bit, 4 bytes on 32-bit. Do not assume function and object pointers have the same representation.
- Enums: Unscoped enum underlying type is implementation defined (usually int). Prefer enum class and specify underlying type if size matters (e.g., enum class Color : std::uint8_t {...}).
- Alignment and padding: Struct members can be padded; sizeof(struct) ≥ sum of member sizes. Use alignof(T) to inspect alignment. Avoid packed layouts unless interfacing with wire formats.
- Endianness: Varies by platform (x86/x86-64 is little endian). For I/O and protocols use explicit byte order conversions; do not reinterpret multi byte values across machines without conversion.
Buffers, arrays, and strings
- C-style strings (char[]): must include space for the NUL terminator '\0'. Example: char buf[6] = "hello"; // 5 chars + 1 terminator
- std::string: Contiguous storage. data() and c_str() point to a NUL-terminated buffer; size() excludes the terminator. capacity() may exceed size(). Growth can reallocate and invalidate pointers/iterators. Small string optimization (SSO) is implementation detail; never rely on a minimum in place capacity.
- std::string_view: Non owning view; not guaranteed NUL-terminated; lifetime must outlive the view; do not store views to temporaries.
- std::array
: Fixed size buffer; sizeof(std::array ) == N * sizeof(T) (no heap); contiguous; bounds unchecked with operator[]. - std::vector
: Dynamic, contiguous buffer; size() vs capacity(); push_back/emplace_back may reallocate. Use reserve() to pre allocate; shrink_to_fit() is non binding. - std::span
(C++20): Non owning view into a contiguous buffer; stores pointer and length (in elements). Does not own memory. - std::byte (C++17): Prefer for raw byte buffers instead of unsigned char when semantics are bytes, not characters.
Printing sizes and detecting ABI at runtime
#include <iostream>
#include <cstdint>
#include <limits>
int main() {
std::cout << "sizeof(void*)=" << sizeof(void*) << "\n";
std::cout << "sizeof(long)=" << sizeof(long) << ", sizeof(long long)=" << sizeof(long long) << "\n";
std::cout << "char is signed? " << std::boolalpha << std::numeric_limits<char>::is_signed << "\n";
bool lp64 = sizeof(long) == 8 && sizeof(void*) == 8;
bool llp64 = sizeof(long) == 4 && sizeof(void*) == 8;
std::cout << "LP64=" << lp64 << ", LLP64=" << llp64 << "\n";
}
Guidance
- Prefer fixed width types from
when size matters (e.g., std::uint32_t). - For binary I/O, use reinterpret_cast
(buf) only at the I/O boundary and ensure sizes are in bytes: count * sizeof(T). - Avoid assuming wchar_t is Unicode scalar value width; prefer char8_t/char16_t/char32_t for explicit encodings and libraries for conversions.
8. Operators¶
- Arithmetic: + * / %
- Comparison: == != < > <= >=
- Logical: && || !
- Bitwise: & | ^ ~ << >>
- Assignment: = += -= *= /= %= &= |= ^= <<= >>=
- Increment/decrement: ++ --
9. Control flow¶
#include <iostream>
int main() {
int x = 7;
if (x > 5) {
std::cout << ">5\n";
} else if (x == 5) {
std::cout << "=5\n";
} else {
std::cout << "<5\n";
}
switch (x) {
case 1: std::cout << "one\n"; break;
case 7: std::cout << "seven\n"; break;
default: std::cout << "other\n"; break;
}
for (int i = 0; i < 3; ++i) {
std::cout << i << ' ';
}
std::cout << '\n';
int y = 0;
while (y < 3) {
std::cout << y++ << ' ';
}
std::cout << '\n';
}
10. Functions¶
- For big objects, pass by
const&to avoid copies. - Returning values is cheap thanks to Return Value Optimization (RVO).
#include <string>
#include <iostream>
std::string greet(const std::string& name) {
return "Hello, " + name + "!";
}
int add(int a, int b) { return a + b; }
int main() {
std::cout << greet("World") << '\n';
std::cout << add(2, 3) << '\n';
}
Overloading and default parameters:
int area(int w, int h) { return w * h; }
int area(int side) { return side * side; } // overload
int volume(int l, int w, int h = 1) { return l * w * h; } // default
11. References vs pointers¶
- Reference (&): must bind to an object, cannot be null, usually safer.
- Pointer (*): can be null, can be reseated, must be managed carefully.
![]()
#include <iostream>
void inc_ref(int& v) { v += 1; }
void inc_ptr(int* v) { if (v) *v += 1; }
int main() {
int x = 10;
inc_ref(x);
inc_ptr(&x);
std::cout << x << '
'; // 12
}
12. Arrays and strings¶
Prefer std::array (fixed size) and std::vector (dynamic) over raw arrays. Prefer std::string.
#include <array>
#include <vector>
#include <string>
#include <iostream>
int main() {
std::array<int, 3> a = {1, 2, 3};
std::vector<int> v = {1, 2, 3, 4};
v.push_back(5);
std::string s = "secure";
s += " coding";
std::cout << a[0] << ' ' << v.back() << ' ' << s << '
';
}
Part 3: The Standard Library-(STL)¶
13. Containers¶
- Sequence: std::vector, std::array, std::deque, std::list, std::forward_list
- Associative: std::set, std::multiset, std::map, std::multimap
- Unordered (hash based): std::unordered_set, std::unordered_map
- Adapters: std::stack, std::queue, std::priority_queue
Use cases:
- Default to std::vector for dynamic arrays
- Use std::map/std::unordered_map for key/value
- Use std::string for text; std::string_view for non owning string slices
#include <vector>
#include <map>
#include <unordered_map>
#include <string>
#include <iostream>
int main() {
std::vector<int> nums = {1,2,3};
nums.insert(nums.begin(), 0);
std::map<std::string, int> ordered{{"alice",1},{"bob",2}};
std::unordered_map<std::string, int> fast{{"alice",1},{"bob",2}};
std::cout << ordered["alice"] << ' ' << fast["bob"] << '
';
}
14. Algorithms¶
Prefer algorithms over manual loops: std::sort, std::transform, std::accumulate, std::find_if, std::any_of, std::all_of, std::remove_if, etc.
#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {4,1,3,2};
std::sort(v.begin(), v.end());
int sum = std::accumulate(v.begin(), v.end(), 0);
bool any_even = std::any_of(v.begin(), v.end(), [](int x){ return x % 2 == 0; });
v.erase(std::remove_if(v.begin(), v.end(), [](int x){ return x < 3; }), v.end());
std::cout << sum << ' ' << any_even << ' ' << v.size() << '
';
}
15. Iterators and Ranges¶
Ranges (C++20) make code concise and safe.
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1,2,3,4,5};
auto squares = v | std::views::transform([](int x){ return x*x; })
| std::views::filter([](int x){ return x % 2 == 0; });
for (int x : squares) std::cout << x << ' '; // 4 16
std::cout << '
';
}
16. Time and chrono¶
#include <chrono>
#include <thread>
#include <iostream>
int main() {
using namespace std::chrono_literals; // enables 100ms, 2s literals
auto start = std::chrono::steady_clock::now();
std::this_thread::sleep_for(100ms);
auto end = std::chrono::steady_clock::now();
std::cout << "Elapsed: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(end start).count()
<< " ms
";
}
17. Filesystem¶
#include <filesystem>
#include <iostream>
int main() {
namespace fs = std::filesystem;
for (auto const& entry : fs::directory_iterator{"."}) {
std::cout << entry.path() << '
';
}
}
18. Formatting and I/O¶
Prefer std::format (C++20/23) for safe, readable formatting.
#include <format>
#include <iostream>
int main() {
std::string s = std::format("User: {} Score: {}", "alice", 42);
std::cout << s << '
';
}
Fallback for older compilers: {fmt} library (https://github.com/fmtlib/fmt).
19. Optional, Variant, Any, Expected¶
- std::optional
: maybe a value - std::variant: tagged union
- std::any: type erased container (avoid unless needed)
- std::expected
(C++23 or via tl::expected): return value or error
#include <optional>
#include <string>
#include <iostream>
std::optional<int> to_int(const std::string& s) {
try {
size_t idx = 0;
int v = std::stoi(s, &idx);
if (idx != s.size()) return std::nullopt;
return v;
} catch (...) {
return std::nullopt;
}
}
int main() {
if (auto v = to_int("123")) std::cout << *v << '
';
}
20. Random¶
Use
#include <random>
#include <iostream>
int main() {
std::mt19937 rng{std::random_device{}()};
std::uniform_int_distribution<int> dist(1, 6);
std::cout << dist(rng) << '
';
}
For cryptography, use dedicated libraries; do not use std::random for secrets.
21. Regex¶
#include <regex>
#include <string>
#include <iostream>
int main() {
std::regex re{"^([a z]+)\d{2}$"};
std::string s = "user42";
std::cout << std::boolalpha << std::regex_match(s, re) << '
';
}
Part 4: Object Oriented Programming-(OOP)¶
22. Classes and structs¶
#include <string>
#include <iostream>
class User {
public:
User(std::string name, int score) : name_(std::move(name)), score_(score) {}
void add_score(int delta) { score_ += delta; }
int score() const { return score_; }
const std::string& name() const { return name_; }
private:
std::string name_;
int score_;
};
int main() {
User u{"alice", 10};
u.add_score(5);
std::cout << u.name() << ": " << u.score() << '
';
}
- struct defaults to public members; class defaults to private
23. Constructors, destructors, copy/move¶
#include <vector>
#include <iostream>
struct Buffer {
std::vector<int> data;
Buffer() = default;
explicit Buffer(size_t n) : data(n) {}
// Rule of 0: let the compiler generate special members when using RAII types
};
int main() {
Buffer a{10};
Buffer b = a; // copy
Buffer c = std::move(a); // move
std::cout << b.data.size() << ' ' << c.data.size() << '
';
}
If you manage raw resources, define copy/move explicitly or disable them. Prefer RAII types to avoid manual management.
24. Inheritance and polymorphism¶
#include <iostream>
#include <memory>
struct Shape {
virtual ~Shape() = default;
virtual double area() const = 0; // pure virtual
};
struct Rect : Shape {
double w{}, h{};
Rect(double w, double h) : w(w), h(h) {}
double area() const override { return w * h; }
};
int main() {
std::unique_ptr<Shape> s = std::make_unique<Rect>(2.0, 3.0);
std::cout << s->area() << '
';
}
Use override, final, and virtual destructors for polymorphic bases.
25. Rule of 0/3/5 and RAII¶
- Rule of 0: if all members are RAII types, no need to define destructor/copy/move.
- Rule of 3: if you define any of destructor, copy ctor, copy assign, define all three.
- Rule of 5: include move ctor and move assign in modern C++.
- RAII: acquire resources in constructor, release in destructor.
![]()
Part 5: Modern C++ Features¶
26. Smart pointers¶
Prefer smart pointers over raw new/delete.
- std::unique_ptr: exclusive ownership
- std::shared_ptr: shared ownership
- std::weak_ptr: non owning reference to shared object
#include <memory>
#include <string>
struct Thing { std::string name; };
std::unique_ptr<Thing> make_unique_thing() {
return std::make_unique<Thing>(Thing{"alpha"});
}
int main() {
auto t = make_unique_thing();
// transfer ownership
auto t2 = std::move(t);
}
Avoid shared_ptr unless sharing is necessary (it has overhead). Avoid cycles with shared_ptr; break them with weak_ptr.
27. Auto, range for, structured bindings¶
#include <map>
#include <string>
#include <iostream>
int main() {
std::map<std::string,int> m{{"a",1},{"b",2}};
for (const auto& [k,v] : m) {
std::cout << k << ": " << v << '
';
}
}
28. Lambdas¶
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v{1,2,3,4};
int threshold = 2;
auto cnt = std::count_if(v.begin(), v.end(), [threshold](int x){ return x > threshold; });
std::cout << cnt << '
';
}
29. Templates and Concepts¶
#include <concepts>
#include <iostream>
template <std::integral T>
T add(T a, T b) { return a + b; }
int main() {
std::cout << add(3, 4) << '
';
}
Concepts provide clear constraints and better error messages.
30. Modules-(C++20+)¶
Modules speed up builds and improve encapsulation. Toolchain support varies.
// math.ixx (interface unit)
export module math;
export int add(int a, int b);
// math.cpp
module math;
export int add(int a, int b) { return a + b; }
// main.cpp
import math;
#include <iostream>
int main(){ std::cout << add(2,3) << '
'; }
31. Coroutines-(C++20+)¶
Coroutines enable async style code. Libraries provide helpers (cppcoro, etc.). Minimal example outlines the idea; production requires promise types/awaitables.
// Pseudocode level sketch, not production ready
// co_return, co_await, and co_yield appear in coroutine functions
32. Ranges-(C++20+)¶
Already shown above; prefer range pipelines for clarity.
Part 6: Concurrency and Parallelism¶
33. Threads and mutexes¶
#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
int main() {
std::mutex m;
int counter = 0;
auto work = [&](){
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(m);
++counter;
}
};
std::thread t1(work), t2(work);
t1.join(); t2.join();
std::cout << counter << '
';
}
34. Atomics¶
#include <atomic>
#include <thread>
#include <vector>
#include <iostream>
int main() {
std::atomic<int> counter{0};
auto work = [&](){ for (int i = 0; i < 100000; ++i) counter.fetch_add(1, std::memory_order_relaxed); };
std::thread t1(work), t2(work);
t1.join(); t2.join();
std::cout << counter.load() << '
';
}
35. Async and futures¶
#include <future>
#include <thread>
#include <iostream>
int compute() { std::this_thread::sleep_for(std::chrono::milliseconds(100)); return 42; }
int main() {
auto fut = std::async(std::launch::async, compute);
std::cout << fut.get() << '
';
}
36. Parallel algorithms¶
#include <algorithm>
#include <execution>
#include <vector>
int main() {
std::vector<int> v(1'000'000, 1);
std::for_each(std::execution::par, v.begin(), v.end(), [](int& x){ x += 1; });
}
Part 7: I/O and Data Handling¶
37. I/O, files, and serialization¶
38. Text and binary files¶
#include <fstream>
#include <vector>
#include <string>
int main() {
// Write text
{
std::ofstream out{"out.txt"};
out << "hello
";
}
// Read text
{
std::ifstream in{"out.txt"};
std::string line;
while (std::getline(in, line)) {
// process line
}
}
// Write binary
{
std::vector<int> data{1,2,3,4};
std::ofstream out{"data.bin", std::ios::binary};
out.write(reinterpret_cast<const char*>(data.data()), data.size()*sizeof(int));
}
// Read binary
{
std::ifstream in{"data.bin", std::ios::binary};
std::vector<int> buf(4);
in.read(reinterpret_cast<char*>(buf.data()), buf.size()*sizeof(int));
}
}
39. JSON (3rd-party)¶
Use nlohmann/json (header only) or simdjson for performance.
// Example with nlohmann/json (https://github.com/nlohmann/json)
#include <nlohmann/json.hpp>
#include <string>
#include <iostream>
int main() {
nlohmann::json j;
j["user"] = "alice";
j["score"] = 42;
std::cout << j.dump(2) << '
';
}
Part 8: Secure Coding Practices¶
40. Secure C++: Do this, avoid that¶
This section prioritizes safe practices. When in doubt, prefer higher level abstractions and library facilities.
41. Avoid dangerous functions¶
- Do not use: gets, strcpy, strcat, sprintf, vsprintf, scanf without width limits, strtol without checks, strtok, asctime, ctime (unsafe variants)
- Prefer: std::string, std::string_view, std::snprintf, std::getline, iostreams, std::from_chars/to_chars
#include <charconv>
#include <string>
#include <system_error>
#include <iostream>
std::optional<int> parse_int(std::string_view sv) {
int value{};
auto* first = sv.data();
auto* last = sv.data() + sv.size();
auto [ptr, ec] = std::from_chars(first, last, value);
if (ec == std::errc{} && ptr == last) return value;
return std::nullopt;
}
int main() {
if (auto v = parse_int("123")) std::cout << *v << '
';
}
42. Bounds and input validation¶
- Always check sizes and bounds.
- Use at() for bounds checked access in safety critical code.
- Validate untrusted input rigorously.
#include <vector>
#include <iostream>
void safe_access(const std::vector<int>& v, size_t i) {
if (i < v.size()) {
std::cout << v[i] << '
';
} else {
std::cout << "index out of range
";
}
}
43. Integer safety¶
- Watch for signed/unsigned conversions and overflow.
- Prefer fixed width types and explicit casts only when necessary.
- Use checked math helpers (e.g., Abseil, SafeInt) in critical code.
#include <cstdint>
#include <limits>
#include <iostream>
bool add_will_overflow(std::uint32_t a, std::uint32_t b) {
return b > std::numeric_limits<std::uint32_t>::max() a;
}
44. Lifetime and ownership¶
- Prefer values and std::unique_ptr for ownership.
- Avoid raw new/delete; avoid manual arrays; use std::vector/std::array.
- Avoid dangling references by ensuring the owner outlives the reference.
#include <memory>
#include <string>
std::unique_ptr<std::string> make_name() {
return std::make_unique<std::string>("alice");
}
45. Undefined behavior and portability¶
- Do not dereference null or invalid pointers.
- Do not access out of bounds.
- Do not violate strict aliasing.
- Keep to the standard; be careful with compiler specific extensions.
46. Filesystem safety¶
- Normalize and validate paths; avoid directory traversal.
- Avoid following untrusted symlinks; use std::filesystem::is_symlink and canonical.
- Restrict file permissions when creating files.
#include <filesystem>
#include <iostream>
bool safe_subpath(const std::filesystem::path& base, const std::filesystem::path& user) {
auto canon_base = std::filesystem::weakly_canonical(base);
auto canon_user = std::filesystem::weakly_canonical(canon_base / user);
return std::ranges::equal(canon_base, canon_user.begin(), canon_base.end());
}
47. Cryptography guidance¶
- Do not implement cryptographic primitives yourself.
- Use vetted libraries (libsodium, Botan, OpenSSL via safe wrappers).
- Use proper randomness for keys (OS-provided CSPRNG). Avoid std::random for secrets.
- Ensure constant time comparisons for secrets where applicable.
48. Concurrency pitfalls¶
- Data races cause undefined behavior.
- Protect shared data with mutexes or atomics.
- Prefer immutable data and message passing when possible.
49. Sanitizers and hardening¶
- AddressSanitizer (ASan): detects out of bounds and use after free
- UndefinedBehaviorSanitizer (UBSan): catches undefined behavior
- ThreadSanitizer (TSan): detects data races
- MemorySanitizer (MSan): uninitialized memory reads
GCC/Clang example (debug build):
- -fsanitize=address,undefined -fno omit frame pointer -g
Binary hardening (Linux):
- -D_FORTIFY_SOURCE=2 -fstack protector strong -fPIE -pie -Wl,-z,relro,-z,now
50. Static analysis and formatting¶
- clang tidy: style and bug finding
- cppcheck: static analysis
- include what you use: header hygiene
- clang format: consistent formatting
Part 9: Build, Test, and Deploy¶
51. Build systems and toolchains¶
52. Compilers-(GCC/Clang/MSVC)¶
- Prefer latest stable versions for better diagnostics and features.
- Enable high warning levels and treat warnings as errors in CI.
GCC/Clang flags:
- -Wall -Wextra -Wpedantic -Wconversion -Wsign conversion -Wshadow -Wnon virtual dtor
MSVC flags:
- /W4 /permissive- /EHsc
53. CMake basics¶
Modern usage: target based commands.
cmake_minimum_required(VERSION 3.23)
project(secproj LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(secproj_lib src/api.cpp)
target_include_directories(secproj_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
add_executable(secproj_app src/main.cpp)
target_link_libraries(secproj_app PRIVATE secproj_lib)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
target_compile_options(secproj_app PRIVATE -Wall -Wextra -Wpedantic)
endif()
54. Unit testing-(GoogleTest)¶
# tests/CMakeLists.txt
add_subdirectory(${CMAKE_SOURCE_DIR}/third_party/googletest googletest build)
add_executable(tests test_api.cpp)
target_link_libraries(tests PRIVATE gtest gtest_main secproj_lib)
add_test(NAME all_tests COMMAND tests)
// tests/test_api.cpp
#include <gtest/gtest.h>
TEST(Sample, Basic) { EXPECT_EQ(2+2, 4); }
Part 10: Advanced Topics & Patterns¶
55. Design patterns and idioms¶
56. Pimpl¶
Hide implementation details to reduce compile time coupling.
// widget.hpp
#include <memory>
class widget {
public:
widget();
~widget();
widget(widget&&) noexcept;
widget& operator=(widget&&) noexcept;
void do_work();
private:
struct impl;
std::unique_ptr<impl> pimpl;
};
// widget.cpp
#include "widget.hpp"
#include <iostream>
struct widget::impl { void do_work_impl(){ std::cout << "work
"; } };
widget::widget() : pimpl(std::make_unique<impl>()) {}
widget::~widget() = default;
widget::widget(widget&&) noexcept = default;
widget& widget::operator=(widget&&) noexcept = default;
void widget::do_work(){ pimpl->do_work_impl(); }
57. Type erasure¶
#include <memory>
#include <utility>
#include <iostream>
class any_drawable {
struct concept_t {
virtual ~concept_t() = default;
virtual void draw() const = 0;
};
template <class T>
struct model_t : concept_t {
T obj;
explicit model_t(T o) : obj(std::move(o)) {}
void draw() const override { obj.draw(); }
};
std::unique_ptr<concept_t> self;
public:
template <class T>
any_drawable(T obj) : self(std::make_unique<model_t<T>>(std::move(obj))) {}
void draw() const { self->draw(); }
};
struct square { void draw() const { std::cout << "[]
"; } };
int main(){ any_drawable d = square{}; d.draw(); }
58. CRTP¶
#include <iostream>
template <class Derived>
struct base {
void interface(){ static_cast<Derived*>(this)->impl(); }
};
struct derived : base<derived> {
void impl(){ std::cout << "CRTP
"; }
};
int main(){ derived d; d.interface(); }
59. Visitor¶
#include <variant>
#include <iostream>
using value = std::variant<int, double, const char*>;
struct visitor_t {
void operator()(int v) const { std::cout << v << '
'; }
void operator()(double v) const { std::cout << v << '
'; }
void operator()(const char* v) const { std::cout << v << '
'; }
};
int main(){ value v = 3.14; std::visit(visitor_t{}, v); }
Part 11: Help and Resources¶
60. Troubleshooting and FAQ¶
- You want help ? remember the golden advice GOOGLE IS YOUR FRIEND
61. Reference links¶
- C++ reference: https://en.cppreference.com/
- Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
- CMake: https://cmake.org/cmake/help/latest/
- Sanitizers: https://github.com/google/sanitizers
- fmt: https://github.com/fmtlib/fmt
- GoogleTest: https://github.com/google/googletest
- Ranges: https://en.cppreference.com/w/cpp/ranges
- Coroutines TS: https://en.cppreference.com/w/cpp/language/coroutines
- Filesystem: https://en.cppreference.com/w/cpp/filesystem
Sources¶
- C++ reference (cppreference): https://en.cppreference.com/
- ISO C++ (WG21) papers and drafts: https://wg21.link/
- C++ Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
- isocpp.org (news, FAQs): https://isocpp.org/
- GCC C++ options: https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html
- Clang driver and diagnostics: https://clang.llvm.org/docs/UsersManual.html
- MSVC C++ docs: https://learn.microsoft.com/cpp/
- CMake documentation: https://cmake.org/cmake/help/latest/
- GoogleTest documentation: https://google.github.io/googletest/
- {fmt} library: https://fmt.dev/latest/index.html
- Ranges (C++20): https://en.cppreference.com/w/cpp/ranges
- Coroutines: https://en.cppreference.com/w/cpp/language/coroutines
- Filesystem: https://en.cppreference.com/w/cpp/filesystem
and PRNGs: https://en.cppreference.com/w/cpp/numeric/random - std::format (C++20/23): https://en.cppreference.com/w/cpp/utility/format
- Sanitizers (ASan/UBSan/TSan/MSan): https://github.com/google/sanitizers
- Abseil (checked integer helpers, base): https://abseil.io/
- Conan/vcpkg package managers: https://conan.io/ | https://vcpkg.io/