Programming foundations: C++

Sefism.

Chapter 03

C++ essentials

Modern C++ in practice: how code is built, references and pointers, who owns memory, and the undefined behaviour you must learn to avoid.

Compiling and linking

Also called g++, clang++, compiler flags, translation unit

What
The compiler turns each .cpp file into an object file, and the linker joins object files and libraries into one executable. Headers share declarations between files.
Why
It explains the two kinds of build errors: compile errors (bad code in one file) and linker errors such as "undefined reference" (a declared function nobody defined or linked).
Shell
g++ -std=c++20 -Wall -Wextra -g main.cpp pricing.cpp -o app
./app
clang++ -std=c++20 -g -fsanitize=address,undefined main.cpp

Watch out

Compiling without warnings: -Wall -Wextra catches uninitialised variables and bad conversions that otherwise become runtime bugs.

References and const

Also called pass by reference, const correctness, &

What
A reference (T&) is another name for an existing object. const marks something that must not change. const T& passes a large object without copying and without allowing changes.
Why
Passing std::string or std::vector by value copies it. const& is the normal way to pass them for reading, and & when the function must modify them.
C++
void greet(const std::string& name) {
  std::cout << "Hi " << name << '\n';
}

void grow(std::vector<int>& values) {
  values.push_back(42);
}

std::vector<int> nums;
grow(nums);

Watch out

Returning a reference to a local variable: the local is destroyed when the function returns and the reference dangles.

Pointers

Also called raw pointers, nullptr, address-of, dereference

What
A pointer holds the memory address of an object. &x takes the address, *p reads or writes the object it points to, and nullptr means it points nowhere.
Why
Pointers underlie arrays, dynamic memory and many C APIs. In modern code, raw pointers mean "I look at this but do not own it"; ownership goes to smart pointers.
C++
int x = 5;
int* p = &x;
*p = 7;
std::cout << x << '\n'; // 7

int* none = nullptr;
if (none != nullptr) {
  std::cout << *none << '\n';
}

Watch out

Dereferencing a null or dangling pointer, which is undefined behaviour and may not crash where the bug is.

RAII

Also called Resource Acquisition Is Initialisation, destructors

What
RAII ties a resource (memory, file, lock) to an object: the constructor acquires it and the destructor releases it when the object goes out of scope, even if an exception is thrown.
Why
It is how C++ avoids leaks without a garbage collector. std::vector, std::string, std::ifstream and std::lock_guard all work this way.
C++
std::mutex m;

void add(std::vector<int>& values, int n) {
  std::lock_guard lock{m};
  values.push_back(n);
}

Watch out

Calling new and delete by hand, then leaking when an early return or exception skips the delete.

Smart pointers

Also called unique_ptr, shared_ptr, make_unique, ownership

What
std::unique_ptr is the single owner of a heap object and deletes it automatically. std::shared_ptr counts owners and deletes when the last one goes. Create them with make_unique and make_shared.
Why
They make ownership visible in the type and remove manual delete. Default to unique_ptr; use shared_ptr only when ownership is truly shared.
C++
auto owner = std::make_unique<Account>("ada");
owner->deposit(100);
auto moved = std::move(owner); // owner is now null

auto shared = std::make_shared<Account>("team");
auto alias = shared; // two owners, one object

Watch out

Using shared_ptr everywhere by habit, which hides who owns what and can leak through reference cycles.

Undefined behaviour

Also called UB

What
Code the C++ standard places no requirements on, such as reading out of bounds, using uninitialised variables, signed integer overflow or using a destroyed object. Anything may happen, including seeming to work.
Why
UB bugs appear only on some machines, compilers or optimisation levels. Sanitizers (-fsanitize=address,undefined) find most of them while you test.
C++
std::vector<int> v{1, 2, 3};
int a = v[3];    // UB: out of bounds, may not crash
int b = v.at(3); // throws std::out_of_range
int x;
std::cout << x;  // UB: reading an uninitialised int

Watch out

Concluding code is correct because it ran fine once: undefined behaviour often works in debug builds and breaks with optimisation on.

Move semantics

Also called std::move, rvalue references, emplace_back

What
Moving transfers the resources of an object (such as the buffer of a string) to another object instead of copying them. std::move marks an object as safe to move from.
Why
It makes returning and storing large objects cheap. Returning a local by value is already optimised, so you rarely need std::move there.
C++
std::vector<std::string> names;
std::string big(1'000'000, 'x');
names.push_back(big);            // copies
names.push_back(std::move(big)); // moves, big is unspecified
names.emplace_back("built in place");

Watch out

Using an object after moving from it: it is valid but its value is unspecified.

03 · C++ essentials3 / 10