Skip to main content
Softwareintermediatecppperformancetemplatesraiismart-pointers

C++ Fundamentals and Performance

Essential C++ techniques, modern features, and performance optimization patterns for systems programming.

25 min read
Updated 9/8/2024
1 prerequisite

Prerequisites

Make sure you're familiar with these concepts before diving in:

Basic Cpp

Learning Objectives

By the end of this topic, you will be able to:

Master modern C++ features: RAII, smart pointers, move semantics
Understand template programming and CRTP patterns
Apply performance optimization techniques
Use constexpr for compile-time computation

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. Use std::make_unique.
  • std::shared_ptr: Shared ownership via reference counting; heavier than unique_ptr.
  • std::weak_ptr: Non-owning observer; breaks cycles; call .lock() to get a shared_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; use std::shared_ptr only for true shared ownership
  • Construct with std::make_unique/std::make_shared; avoid raw new
  • Express ownership transfer with std::unique_ptr by value
  • Break std::shared_ptr cycles with std::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: constconstexpr (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.