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
1. Performance Tips
1.1 Avoid std::endl
in Loops
\n
is faster in loops. std::endl()
flushes every time, causing performance overhead.
1.2 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;
}
1.3 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
2. 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.
3. Smart Pointers (RAII for Ownership)
3.1 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
.
3.2 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;
}
3.3 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
}
3.4 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
}
3.5 Smart Pointer Best Practices
- Prefer
std::unique_ptr
by default; usestd::shared_ptr
only for true shared ownership - Construct with
std::make_unique
/std::make_shared
; avoid rawnew
- Express ownership transfer with
std::unique_ptr
by value - Break
std::shared_ptr
cycles withstd::weak_ptr
4. Move Semantics
Concept: Steal resources instead of cloning for fast transfers of large objects.
4.1 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
}
4.2 Move Semantics Best Practices
- Mark move constructors/assignments
noexcept
for container optimization - Use pass-by-value + move for sink parameters:
void set(std::string s){ member = std::move(s); }
- Don't move from
const
objects; after moving, source is valid-but-unspecified - Prefer returning by value; rely on NRVO/move over manual allocation
4.3 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);
5. constexpr
(Compile-Time Computation)
Purpose: Functions/objects evaluated at compile time when given constant arguments.
5.1 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 time
5.2 Conditional 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;
}
5.3 constexpr Best Practices
- Mark pure, small, frequently-used functions
constexpr
- Use
consteval
when runtime use would be a bug - Remember:
const
≠constexpr
(const
is runtime-initialized) - Avoid I/O, dynamic allocation, and UB in
constexpr
code
6. Templates and Concepts
6.1 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>
6.2 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; }
6.3 Template Best Practices
- Constrain templates with concepts for better diagnostics
- Prefer composition over specialization
- Beware code bloat; use runtime polymorphism when simpler
7. CRTP (Curiously Recurring Template Pattern)
Purpose: Static polymorphism without virtual function overhead.
7.1 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(); }
7.2 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; }
};
7.3 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
8. Hardware-Aware Programming
8.1 Cache Line Information
// C++17 features for cache optimization
std::hardware_destructive_interference_size // avoid false sharing
std::hardware_constructive_interference_size // encourage sharing
8.2 Mathematical Functions
#include <cmath>
std::ceil() // ceiling function
std::floor() // floor function
9. Resource 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.