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
.cppfile 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).
g++ -std=c++20 -Wall -Wextra -g main.cpp pricing.cpp -o app
./app
clang++ -std=c++20 -g -fsanitize=address,undefined main.cppWatch 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.constmarks something that must not change.const T&passes a large object without copying and without allowing changes. - Why
- Passing
std::stringorstd::vectorby value copies it.const&is the normal way to pass them for reading, and&when the function must modify them.
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.
&xtakes the address,*preads or writes the object it points to, andnullptrmeans 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.
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::ifstreamandstd::lock_guardall work this way.
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_ptris the single owner of a heap object and deletes it automatically.std::shared_ptrcounts owners and deletes when the last one goes. Create them withmake_uniqueandmake_shared.- Why
- They make ownership visible in the type and remove manual
delete. Default tounique_ptr; useshared_ptronly when ownership is truly shared.
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 objectWatch 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.
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 intWatch 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::movemarks 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::movethere.
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.