C++ Fundamentals and Performance
Essential C++ techniques, modern features, and performance optimization patterns for systems programming.
Prerequisites
Make sure you're familiar with these concepts before diving in:
Learning Objectives
By the end of this topic, you will be able to:
Table of Contents
C++ Fundamentals and Performance
Performance Tips
Avoid std::endl in Loops
\n is faster in loops. std::endl() flushes every time, causing performance overhead.
Precise Output with std::fixed & std::setprecision()
int main() {
double num = 123.456789;
std::cout << std::fixed << std::setprecision(2) << num << std::endl;
// Output: 123.46
return 0;
}Efficient Sorting and Accumulation
- Sort:
std::sort(arr.begin(), arr.end())- O(n log n) time - Sum:
std::accumulate()for container summation - Selection:
std::nth_element()for O(n) selection without full sorting
Modern C++ Feature Overview
Core Features: RAII, smart pointers, move semantics, constexpr, templates, CRTP.
Performance Habits: Data-oriented design, avoiding heap churn, minimizing virtual calls.
Tooling: CMake, clang-tidy, sanitizers, gcov/llvm-cov, valgrind/perf.
Smart Pointers (RAII for Ownership)
Types and Usage
std::unique_ptr: Sole ownership; movable, not copyable. Usestd::make_unique.std::shared_ptr: Shared ownership via reference counting; heavier thanunique_ptr.std::weak_ptr: Non-owning observer; breaks cycles; call.lock()to get ashared_ptr.
Unique Pointer Example
#include <memory>
struct Foo { int x{0}; };
int main() {
// unique_ptr: default choice for ownership
auto up = std::make_unique<Foo>();
up->x = 42;
// transfer ownership
std::unique_ptr<Foo> up2 = std::move(up);
// up is now null; up2 owns the object
return up ? 1 : 0;
}Shared Pointer with Cycle Breaking
#include <memory>
struct Node {
int value{};
std::shared_ptr<Node> next; // owning forward link
std::weak_ptr<Node> prev; // non-owning back link to avoid cycle
};
int main() {
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b; // a owns b
b->prev = a; // b observes a without owning
}Custom Deleter for C Resources
#include <cstdio>
#include <memory>
int main() {
// unique_ptr with custom deleter (manages C resource)
std::unique_ptr<FILE, int(*)(FILE*)> file(std::fopen("data.txt", "r"), std::fclose);
if (!file) return 1;
// use file.get() with C APIs
}Smart Pointer Best Practices
- Prefer
std::unique_ptrby default; usestd::shared_ptronly for true shared ownership - Construct with
std::make_unique/std::make_shared; avoid rawnew - Express ownership transfer with
std::unique_ptrby value - Break
std::shared_ptrcycles withstd::weak_ptr
Move Semantics
Concept: Steal resources instead of cloning for fast transfers of large objects.
Move-Only Type Example
#include <vector>
struct Buffer {
std::vector<int> data;
Buffer() = default;
Buffer(const Buffer&) = delete; // move-only
Buffer& operator=(const Buffer&) = delete;
Buffer(Buffer&&) noexcept = default; // noexcept enables more moves
Buffer& operator=(Buffer&&) noexcept = default;
};
Buffer make() {
Buffer b; b.data.resize(1'000'000);
return b; // NRVO or move
}
int main() {
Buffer a = make(); // moved/NRVO
std::vector<Buffer> v;
v.push_back(std::move(a)); // force move
}Move Semantics Best Practices
- Mark move constructors/assignments
noexceptfor container optimization - Use pass-by-value + move for sink parameters:
void set(std::string s){ member = std::move(s); } - Don't move from
constobjects; after moving, source is valid-but-unspecified - Prefer returning by value; rely on NRVO/move over manual allocation
Efficient Storage Patterns
std::vector<TraceRec> v;
v.reserve(N);
TraceRec rec = /*...*/;
v.push_back(std::move(rec));
// or construct in place:
v.emplace_back(ts, op, addr, size);constexpr (Compile-Time Computation)
Purpose: Functions/objects evaluated at compile time when given constant arguments.
Basic constexpr Usage
#include <array>
#include <type_traits>
constexpr int square(int x) { return x * x; }
static_assert(square(5) == 25);
constexpr int N = square(8);
std::array<int, N> arr{}; // size known at compile timeConditional constexpr
#include <cmath>
#include <type_traits>
template <class T>
constexpr auto norm(T v) {
if constexpr (std::is_floating_point_v<T>) return std::fabs(v);
else return v < 0 ? -v : v;
}constexpr Best Practices
- Mark pure, small, frequently-used functions
constexpr - Use
constevalwhen runtime use would be a bug - Remember:
const≠constexpr(constis runtime-initialized) - Avoid I/O, dynamic allocation, and UB in
constexprcode
Templates and Concepts
Basic Template Usage
template <class T>
T min_value(const T& a, const T& b) { return b < a ? b : a; }
template <class T>
struct Box { T value; };
Box b{42}; // CTAD (C++17) deduces Box<int>C++20 Concepts
#include <type_traits>
template <typename T>
concept Arithmetic = std::is_arithmetic_v<T>;
template <Arithmetic T>
T add(T a, T b) { return a + b; }Template Best Practices
- Constrain templates with concepts for better diagnostics
- Prefer composition over specialization
- Beware code bloat; use runtime polymorphism when simpler
CRTP (Curiously Recurring Template Pattern)
Purpose: Static polymorphism without virtual function overhead.
Basic CRTP Pattern
// Base template calls into Derived without virtual overhead
template <class Derived>
struct Drawable {
void draw() { static_cast<Derived*>(this)->draw_impl(); }
};
struct Circle : Drawable<Circle> {
void draw_impl() {/* ... */}
};
int main(){ Circle c; c.draw(); }Mix-in Pattern
template <class Derived>
struct EnableId {
int id() const { return static_cast<const Derived*>(this)->id_impl(); }
};
struct User : EnableId<User> {
int id_impl() const { return 7; }
};CRTP Best Practices
- Use for zero-overhead polymorphism and mix-ins
- Always
static_cast<Derived*>(this)for downcasting - Keep CRTP bases header-only and simple
Hardware-Aware Programming
Cache Line Information
// C++17 features for cache optimization
std::hardware_destructive_interference_size // avoid false sharing
std::hardware_constructive_interference_size // encourage sharingMathematical Functions
#include <cmath>
std::ceil() // ceiling function
std::floor() // floor functionResource Allocation Is Initialization (RAII)
RAII ensures automatic resource management by tying resource lifetime to object lifetime. Resources are acquired in constructors and released in destructors, preventing leaks and ensuring exception safety.