Skip to content

C++ Hoja de referencia

Lenguaje de propósito general con OOP, genéricos y control de bajo nivel.

01

Primeros Pasos

Hola Mundo

Todo programa C++ comienza en main(). <iostream> proporciona std::cout (salida estándar) y std::cin (entrada estándar). El prefijo std:: se refiere al namespace estándar; using namespace std; puede evitarlo pero se desaconseja en cabeceras porque contamina el namespace global.

cpp
#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

Variables y auto

auto deduce el tipo del inicializador (C++11). Use auto cuando el tipo es obvio o verboso (iteradores). const hace un valor inmutable; constexpr evalúa en tiempo de compilación para constantes verdaderas embebidas en el binario.

cpp
int age = 30;
double pi = 3.14159;
char grade = 'A';
bool is_dev = true;
std::string name = "Alice";
auto x = 42;          // int
auto y = 3.14;        // double
const double TAX = 0.08;

Entrada y Salida

std::getline lee una línea completa incluyendo espacios, mientras que std::cin >> se detiene en espacios en blanco. Mezclarlos deja una nueva línea en el buffer; llame std::cin.ignore() entre getline y >> para descartarla.

cpp
#include <iostream>
#include <string>

int main() {
    std::string name;
    int age;
    std::cout << "Name: ";
    std::getline(std::cin, name);
    std::cout << "Age: ";
    std::cin >> age;
    std::cout << "Hi " << name << ", " << age << "\n";
}

Referencias

Las referencias son alias que deben inicializarse y no pueden reasignarse a otro objeto. El pasaje por referencia evita copiar y permite modificar la variable del llamador. Use const T& para parámetros de solo lectura para evitar copias costosas.

cpp
int x = 10;
int &ref = x;   // alias for x
ref = 20;
std::cout << x;   // 20

void increment(int &n) { n++; }
int a = 5;
increment(a);   // a is now 6

Conversión de Tipo

Prefiera static_cast sobre casts estilo C porque es explícito y verificado en tiempo de compilación, haciendo la intención clara. std::stoi, std::stod convierten cadenas a números; std::to_string hace lo inverso. Vigile std::out_of_range en entrada mala.

cpp
double d = 3.99;
int i = (int)d;               // C-style: 3
int j = static_cast<int>(d);  // C++: 3

int n = 65;
char c = static_cast<char>(n);  // 'A'

// string <-> number
int num = std::stoi("42");
std::string s = std::to_string(3.14);
02

Cadenas

Fundamentos de std::string

std::string gestiona su propia memoria y crece según sea necesario. A diferencia de los arrays de char de C, no gestiona la longitud manualmente. .find() devuelve std::string::npos (un valor enorme) cuando no se encuentra la subcadena, así que siempre compare contra npos.

cpp
#include <string>
std::string s = "Hello";
std::cout << s.length();      // 5
std::cout << s[0];            // H
s += ", World!";
std::cout << s;               // Hello, World!
std::cout << s.substr(0, 3);  // Hel
std::cout << s.find("World"); // 7

Comparación y Búsqueda

La comparación es lexicográfica (orden de diccionario). .find() busca hacia adelante, .rfind() busca hacia atrás. Ambos devuelven std::string::npos si no se encuentran, así que siempre compare contra npos en lugar de tratar el resultado como booleano.

cpp
std::string a = "apple", b = "banana";
if (a == b) { /* equal */ }
if (a < b) { /* apple comes before banana */ }
if (a.find("pp") != std::string::npos) {
    std::cout << "found\n";
}
size_t pos = a.rfind("p");  // last occurrence

Stringstream

stringstream hace de puente entre cadenas y valores tipados, útil para construir cadenas formateadas (como un buffer) o parsear tokens separados por espacios en blanco. Es más lento que operaciones directas pero muy flexible para serialización y deserialización.

cpp
#include <sstream>
// Build a string
std::ostringstream oss;
oss << "Name=" << "Alice" << "&Age=" << 30;
std::string result = oss.str();

// Parse tokens
std::istringstream iss("10 20 30");
int a, b, c;
iss >> a >> b >> c;   // a=10, b=20, c=30

Cadenas Raw y Multilínea

Los raw string literals R"(...)" tratan backslashes y comillas literalmente, ideales para patrones regex, rutas de archivo Windows y plantillas JSON/XML. Los delimitadores dentro de los paréntesis son arbitrarios, p. ej. R"x(...)x" para permitir ) dentro.

cpp
std::string raw = R"(C:\Users\name\file.txt)";
// No need to escape backslashes

std::string json = R"({
  "name": "Alice",
  "age": 30
})";

Arrays de char vs std::string

Los arrays de char estilo C requieren gestión manual de tamaño y son propensos a errores (buffer overflows). Prefiera std::string; use .c_str() al interfazar con APIs de C que esperan const char*. Note que c_str() solo es válido mientras la cadena esté viva y sin modificar.

cpp
char cstr[] = "Hello";   // null-terminated, size 6
std::string cppstr = "Hello";

#include <cstring>
std::cout << strlen(cstr);      // 5
std::cout << cppstr.length();   // 5

std::string from_c = cstr;          // C -> C++
const char* to_c = cppstr.c_str();  // C++ -> C
03

Números y Matemáticas

Tipos Enteros y de Coma Flotante

Use tipos de ancho fijo de <cstdint> (int32_t, int64_t) cuando el tamaño exacto importa entre plataformas. El separador de dígitos ' (C++14) mejora la legibilidad de números grandes. double es el tipo de coma flotante predeterminado y se prefiere sobre float por precisión.

cpp
#include <cstdint>
int32_t a = 100;
int64_t big = 9'000'000'000LL;
uint8_t byte = 255;
size_t sz = sizeof(a);   // 4

float f = 1.5f;          // 4 bytes
double d = 3.14159265;   // 8 bytes (preferred)

Límites Numéricos

<limits> proporciona type traits para propiedades numéricas. Úselos en lugar de macros INT_MAX hardcoded. epsilon() da la diferencia más pequeña distinguible por coma flotante, útil para comparar doubles con tolerancia.

cpp
#include <limits>
std::cout << std::numeric_limits<int>::max();    // 2147483647
std::cout << std::numeric_limits<int>::min();    // -2147483648
std::cout << std::numeric_limits<double>::infinity();
std::cout << std::numeric_limits<double>::epsilon();

Funciones Matemáticas

<cmath> proporciona funciones matemáticas estándar. El overflow de enteros es comportamiento indefinido en C++; use int64_t o verifique límites. Para código financiero, recuerde que la coma flotante es imprecisa — considere centavos enteros o una biblioteca decimal.

cpp
#include <cmath>
double x = 2.5;
std::pow(x, 3);     // 15.625
std::sqrt(x);       // 1.581
std::abs(-5);       // 5
std::floor(3.7);    // 3.0
std::ceil(3.2);     // 4.0
std::round(3.5);    // 4.0
std::fmod(10.5, 3); // 1.5

Números Aleatorios

C++ moderno usa la biblioteca <random> en lugar de rand(). mt19937 es un PRNG de alta calidad. Las distribuciones (uniform_int, uniform_real, normal) mapean bits raw al rango deseado sin el sesgo de módulo que plaga rand() % N.

cpp
#include <random>
std::random_device rd;
std::mt19937 gen(rd());  // Mersenne Twister engine
std::uniform_int_distribution<int> dist(1, 100);

for (int i = 0; i < 5; i++) {
    std::cout << dist(gen) << " ";
}

Overflow de Enteros y Casts

El overflow de enteros con signo es comportamiento indefinido en C++ (el compilador puede optimizar asumiendo que nunca ocurre). Siempre castee a un tipo más ancho antes de multiplicar, o verifique límites. El overflow unsigned envuelve módulo 2^n y está bien definido.

cpp
int a = 100'000;
int b = a * a;   // overflow! undefined behavior
long long safe = (long long)a * a;  // OK

// Check before multiplying
if (a > 0 && b > INT_MAX / a) {
    // would overflow, handle it
}
04

Flujo de Control

If / Else

C++17 introduce if con inicializador: if (auto it = m.find(k); it != m.end()) { ... }. Esto scopea la variable al bloque if/else, manteniendo el scope circundante limpio y evitando reuso accidental.

cpp
int score = 85;
if (score >= 90) {
    std::cout << "A\n";
} else if (score >= 80) {
    std::cout << "B\n";
} else {
    std::cout << "C\n";
}

Switch

Siempre incluya break para prevenir caída no intencionada. El atributo [[fallthrough]] de C++17 documenta caída intencional para silenciar advertencias. Switch funciona en tipos integrales y enum, no cadenas o floats.

cpp
int day = 3;
switch (day) {
    case 1: std::cout << "Mon"; break;
    case 2: std::cout << "Tue"; break;
    case 3: std::cout << "Wed"; break;
    default: std::cout << "Other";
}

Bucles For

El for basado en rango itera contenedores de forma limpia. Use const auto& para evitar copiar elementos (importante para cadenas y objetos grandes). Para modificar elementos in place, use auto& (referencia no-const).

cpp
// Classic for
for (int i = 0; i < 5; i++) {
    std::cout << i << " ";
}

// Range-based for (C++11)
std::vector<int> v = {1, 2, 3};
for (int n : v) std::cout << n;
for (const auto &n : v) std::cout << n;  // no copy

While y Do-While

while verifica la condición antes de ejecutar; do-while ejecuta el cuerpo al menos una vez antes de verificar. do-while es útil para validación de entrada y bucles de menú donde el cuerpo debe ejecutarse antes de que la condición pueda evaluarse.

cpp
int n = 5;
while (n > 0) {
    std::cout << n-- << " ";
}

int x;
do {
    std::cin >> x;
} while (x < 0);  // runs at least once

Break, Continue y Bucles Anidados

break sale del bucle envolvente más cercano; continue salta a la siguiente iteración. C++ no tiene break etiquetado como Java; use una variable flag, o extraiga el bucle a una función y use return para salir de bucles anidados.

cpp
for (int i = 0; i < 10; i++) {
    if (i == 3) continue;  // skip 3
    if (i == 7) break;     // stop at 7
    std::cout << i << " ";  // 0 1 2 4 5 6
}

// C++ has no labeled break; use a flag
bool found = false;
for (int i = 0; i < n && !found; i++) {
    for (int j = 0; j < m; j++) {
        if (matrix[i][j] == target) { found = true; break; }
    }
}
05

Funciones y Lambdas

Definir y Múltiples Retornos

Los structured bindings de C++17 (auto [a, b] = ...) desempaquetan tuples, pairs y structs de forma limpia. Antes de C++17, use std::tie o parámetros de salida. Devolver por valor es barato debido a move semantics (RVO) que elide la copia.

cpp
#include <tuple>
std::tuple<int, int> divide(int a, int b) {
    return {a / b, a % b};
}

auto [q, r] = divide(17, 5);  // q=3, r=2 (C++17)

Default e Inline

Los argumentos predeterminados permiten a los llamadores omitir parámetros finales. inline es una pista al compilador para expandir la función inline; los compiladores modernos deciden el inlining ellos mismos basándose en flags de optimización, así que inline es mayormente sobre ODR (one definition rule).

cpp
inline int power(int base, int exp = 2) {
    int r = 1;
    for (int i = 0; i < exp; i++) r *= base;
    return r;
}
// power(3) == 9, power(2, 5) == 32

Sobrecarga de Funciones

La sobrecarga permite que funciones compartan un nombre pero difieran en tipos de parámetros. El compilador elige la mejor coincidencia vía resolución de sobrecarga. Las sobrecargas ambiguas causan errores de compilación; prefiera templates cuando el cuerpo es idéntico entre tipos.

cpp
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
std::string add(std::string a, std::string b) { return a + b; }

add(1, 2);          // int version
add(1.5, 2.5);      // double version
add("a", "b");      // string version

Expresiones Lambda

Las lambdas crean objetos de función anónimos inline. El [] captura variables: [=] por valor, [&] por referencia, [x] específica por valor, [&x] específica por referencia. Son esenciales para algoritmos STL y callbacks. Tenga cuidado con referencias dangling al capturar por referencia.

cpp
auto square = [](int x) { return x * x; };
std::cout << square(5);  // 25

int factor = 3;
auto multiply = [factor](int x) { return x * factor; };

std::vector<int> v = {1, 2, 3};
std::for_each(v.begin(), v.end(), [](int n) {
    std::cout << n << " ";
});

Punteros a Función y std::function

std::function (de <functional>) mantiene cualquier callable: funciones, lambdas, functors. Es más flexible que punteros a función raw pero tiene un pequeño overhead en runtime debido a type erasure. Úselo para callbacks y almacenar callables en contenedores.

cpp
#include <functional>
int add(int a, int b) { return a + b; }

int (*fp)(int, int) = add;             // raw function pointer
std::function<int(int, int)> f = add;  // flexible wrapper
f = [](int a, int b) { return a * b; };

std::cout << f(3, 4);  // 12
06

Contenedores STL

vector

vector es un array dinámico y la elección de contenedor predeterminada. push_back es amortizado O(1). .at() hace verificación de límites (lanza std::out_of_range), operator[] no. Llame reserve() por adelantado si conoce el tamaño para evitar reasignaciones.

cpp
#include <vector>
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.pop_back();
v.size();          // 3
v[0] = 0;
v.at(5);           // throws std::out_of_range
for (int n : v) std::cout << n;

map y unordered_map

map mantiene las claves ordenadas (operaciones O(log n)); unordered_map usa hashing (O(1) promedio). Use map cuando necesite iteración ordenada o consultas por rango; unordered_map para pura velocidad de lookup. Iterar unordered_map es desordenado.

cpp
#include <map>
#include <unordered_map>
std::map<std::string, int> ordered;   // sorted by key (red-black tree)
ordered["Alice"] = 30;

std::unordered_map<std::string, int> hashed;  // hash table
hashed["Bob"] = 25;

for (auto &[k, v] : ordered) {  // C++17 structured binding
    std::cout << k << ":" << v;
}

set y unordered_set

set almacena elementos únicos ordenados (O(log n)). unordered_set es la versión basada en hash (O(1) promedio). Úselos para desduplicación y testing de pertenencia. lower_bound/upper_bound habilitan consultas por rango en sets ordenados.

cpp
#include <set>
std::set<int> s = {3, 1, 4, 1, 5};
// s contains: 1, 3, 4, 5 (sorted, unique)
s.insert(2);
s.erase(1);
if (s.count(4)) std::cout << "found";
auto it = s.lower_bound(3);  // first >= 3

array y deque

array es un array de tamaño fijo asignado en la pila con interfaz STL (más seguro que arrays de C, sin decaer a puntero). deque (cola de doble extremo) soporta push/pop O(1) en ambos extremos, a diferencia de vector que es O(n) al frente.

cpp
#include <array>
#include <deque>
std::array<int, 3> arr = {1, 2, 3};  // fixed size, stack-allocated
arr.size();  // 3

std::deque<int> dq = {1, 2, 3};
dq.push_front(0);
dq.push_back(4);
// dq: 0, 1, 2, 3, 4

tuple y pair

tuple mantiene valores heterogéneos de cualquier tipo. pair es un tuple de 2 elementos. Los structured bindings (C++17) los descomponen en variables con nombre. Común al iterar maps cuyos elementos son pares de (clave, valor).

cpp
#include <tuple>
std::tuple<int, std::string, double> t = {1, "Alice", 3.14};
auto [id, name, val] = t;  // C++17 structured binding

std::pair<int, int> p = {1, 2};
std::cout << p.first << p.second;

auto [a, b] = std::make_pair(10, 20);
07

Punteros y Memoria

Punteros Raw

Los punteros almacenan direcciones de memoria. & obtiene la dirección, * desreferencia. La aritmética de punteros funciona en arrays. Los punteros raw no rastrean ownership, llevando a fugas y punteros dangling — prefiera smart pointers para recursos con ownership.

cpp
int x = 10;
int *ptr = &x;        // address of x
std::cout << *ptr;    // 10 (dereference)
*ptr = 20;
std::cout << x;       // 20

int arr[] = {1, 2, 3};
int *p = arr;
std::cout << *(p + 1);  // 2

Referencias vs Punteros

Las referencias son más seguras (nunca null, siempre válidas) y tienen sintaxis más limpia. Use referencias para parámetros de función y valores de retorno. Use punteros cuando null es un estado significativo o cuando necesita reasignar a qué se apunta.

cpp
int x = 10;
int &ref = x;    // must init, cannot reseat
int *ptr = &x;   // can be null, can reassign

ref = 20;        // x = 20
*ptr = 30;       // x = 30

// References cannot be null, safer for parameters
void foo(const std::string &s);  // preferred
void bar(std::string *s);        // s might be null

unique_ptr

unique_ptr es ownership exclusivo de un objeto del heap. No puede copiarse, solo moverse. Se elimina automáticamente cuando sale del scope (RAII). Este es el smart pointer predeterminado para la mayoría de casos de uso — overhead cero sobre un puntero raw.

cpp
#include <memory>
auto p = std::make_unique<int>(42);
std::cout << *p;  // 42
// auto p2 = p;   // ERROR: cannot copy
auto p2 = std::move(p);  // transfer ownership
// p is now nullptr

shared_ptr y weak_ptr

shared_ptr usa reference counting; el objeto se libera cuando el último shared_ptr se destruye. weak_ptr observa sin afectar el conteo, rompiendo ciclos de referencia. Evite ciclos de shared_ptr (fugan porque el conteo nunca llega a cero).

cpp
#include <memory>
auto a = std::make_shared<int>(42);
auto b = a;  // both point to same object
std::cout << a.use_count();  // 2

std::weak_ptr<int> w = a;  // observer, no ownership
if (auto locked = w.lock()) {
    std::cout << *locked;  // 42
}

RAII y new/delete

RAII vincula el lifetime del recurso al scope del objeto: los constructores adquieren, los destructores liberan. Esto garantiza limpieza incluso cuando se propagan excepciones. Prefiera vectors y smart pointers sobre new/delete manual — implementan RAII por usted.

cpp
// Manual new/delete (avoid in modern C++)
int *p = new int(42);
delete p;

// RAII: resource acquisition is initialization
class Buffer {
    int *data;
public:
    Buffer(size_t n) : data(new int[n]) {}
    ~Buffer() { delete[] data; }  // auto cleanup
};

// Modern: use containers/smart pointers instead
std::vector<int> buf(100);  // no manual delete needed
08

Clases y OOP

Clase y Constructor

La member initializer list (: name(...), age(...)) inicializa miembros antes de que se ejecute el cuerpo, más eficiente que la asignación en el cuerpo. Marque getters const para permitir llamarlos en objetos const. std::move evita copiar el parámetro string.

cpp
class Person {
    std::string name;
    int age;
public:
    Person(std::string n, int a) : name(std::move(n)), age(a) {}
    std::string getName() const { return name; }
    int getAge() const { return age; }
};

Person p("Alice", 30);
std::cout << p.getName();

Modificadores de Acceso y Encapsulación

Los miembros private son accesibles solo dentro de la clase; protected permite subclases; public es abierto a todos. La encapsulación oculta detalles de implementación, exponiendo una interfaz estable. Use private por defecto y exponga solo lo necesario.

cpp
class Account {
private:
    double balance;
protected:
    std::string owner;
public:
    Account(double b) : balance(b) {}
    double getBalance() const { return balance; }
    void deposit(double amt) { if (amt > 0) balance += amt; }
};

Herencia y Virtual

virtual habilita polimorfismo en runtime — llamar speak() a través de un Animal* dispatcha a la versión de Dog. Siempre declare un destructor virtual en clases base para que eliminar a través de un puntero base llame al destructor derivado. override captura errores tipográficos.

cpp
class Animal {
public:
    virtual void speak() { std::cout << "..."; }
    virtual ~Animal() = default;  // virtual destructor!
};

class Dog : public Animal {
public:
    void speak() override { std::cout << "Woof"; }
};

Animal *a = new Dog();
a->speak();  // Woof (polymorphism)
delete a;

Clases Abstractas e Interfaces

Una función virtual pura (= 0) hace la clase abstracta — no puede instanciarla. Las clases con solo virtuales puras actúan como interfaces de Java. Las subclases concretas deben implementar todas las virtuales puras o permanecen abstractas.

cpp
class Shape {
public:
    virtual double area() const = 0;  // pure virtual
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double r;
public:
    Circle(double r) : r(r) {}
    double area() const override {
        return 3.14159 * r * r;
    }
};

Sobrecarga de Operadores

La sobrecarga de operadores permite que los tipos de usuario funcionen con +, <<, ==, etc. Sobrecargue solo cuando el significado sea intuitivo (tipos matemáticos, iteradores). El operador << se sobrecarga comúnmente vía friend para salida de stream, habilitando cout << myObject.

cpp
class Vec2 {
    double x, y;
public:
    Vec2(double x, double y) : x(x), y(y) {}
    Vec2 operator+(const Vec2 &o) const {
        return {x + o.x, y + o.y};
    }
    friend std::ostream &operator<<(std::ostream &os, const Vec2 &v) {
        return os << "(" << v.x << "," << v.y << ")";
    }
};

Vec2 a(1, 2), b(3, 4);
std::cout << a + b;  // (4,6)
09

Templates y Genéricos

Function Templates

Los function templates generan versiones específicas de tipo en tiempo de compilación. El compilador deduce T de los argumentos; también puede especificarlo explícitamente. Los templates son abstracciones de coste cero — sin overhead en runtime, pero aumentan el tiempo de compilación y el tamaño del binario.

cpp
template <typename T>
T max_val(T a, T b) {
    return (a > b) ? a : b;
}

std::cout << max_val(3, 7);           // int: 7
std::cout << max_val(3.14, 2.71);     // double: 3.14
std::cout << max_val<std::string>("a", "b");  // explicit

Class Templates

Los class templates parametrizan clases enteras sobre tipos. Los contenedores estándar (vector, map) son todos templates. El código template debe estar en cabeceras (o usar instanciación explícita) porque el compilador necesita la definición completa para generar código.

cpp
template <typename T>
class Stack {
    std::vector<T> data;
public:
    void push(T v) { data.push_back(v); }
    T pop() { T v = data.back(); data.pop_back(); return v; }
    bool empty() const { return data.empty(); }
};

Stack<int> si;
si.push(1); si.push(2);
Stack<std::string> ss;
ss.push("hi");

Especialización de Template

La especialización completa proporciona una implementación personalizada para un tipo específico. La especialización parcial (solo para class templates) personaliza para una categoría de tipos (p. ej., todos los tipos puntero). Útil para optimizar o casos especiales de comportamiento.

cpp
template <typename T>
T identity(T x) { return x; }

// Full specialization for bool
template <>
bool identity<bool>(bool x) {
    std::cout << "bool!";
    return x;
}

identity(42);     // generic
identity(true);   // specialized

Variadic Templates

Los variadic templates aceptan cualquier número de argumentos vía parameter packs (...). Recursan para procesar cada argumento. Las fold expressions de C++17 simplifican esto: (std::cout << ... << args). Usados intensivamente en std::make_shared, std::tuple.

cpp
template <typename T>
void print(T v) { std::cout << v << "\n"; }

template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << ", ";
    print(rest...);  // recurse
}

print(1, "hello", 3.14);  // 1, hello, 3.14

Concepts (C++20)

Concepts (C++20) restringen parámetros de template con requisitos legibles, reemplazando arcano enable_if/SFINAE. Producen mensajes de error mucho más claros cuando no se cumplen las restricciones. Use concepts estándar como std::integral, std::floating_point, std::convertible_to.

cpp
#include <concepts>
template <std::integral T>
T add(T a, T b) { return a + b; }

template <typename T>
requires std::floating_point<T>
T half(T x) { return x / 2; }

// Abbreviated form
auto square(std::integral auto x) { return x * x; }
10

Algoritmos STL

sort y find

Los algoritmos STL operan en rangos de iteradores [begin, end). sort es O(n log n). find es lineal; para rangos ordenados use binary_search/lower_bound (O(log n)). Pase comparadores personalizados (lambdas o std::greater) para ordenación personalizada.

cpp
#include <algorithm>
std::vector<int> v = {3, 1, 4, 1, 5, 9};
std::sort(v.begin(), v.end());          // 1 1 3 4 5 9
std::sort(v.begin(), v.end(), std::greater<>());  // descending

auto it = std::find(v.begin(), v.end(), 4);
if (it != v.end()) std::cout << "found";

transform y for_each

transform mapea cada elemento a un nuevo valor (como map en lenguajes funcionales). for_each aplica una función para side effects. Los ranges de C++20 permiten v | views::transform(...) para un estilo de pipeline más limpio sin iteradores begin/end.

cpp
std::vector<int> v = {1, 2, 3, 4};
std::vector<int> squared(v.size());
std::transform(v.begin(), v.end(), squared.begin(),
               [](int x) { return x * x; });
// squared: 1 4 9 16

std::for_each(v.begin(), v.end(), [](int &x) { x *= 2; });
// v: 2 4 6 8

accumulate y count

accumulate (de <numeric>) pliega un rango con una operación. El tercer argumento es el valor inicial y determina el tipo de resultado — use 0.0 para sumas double. count devuelve cuántos elementos igualan un valor; count_if usa un predicado.

cpp
#include <numeric>
std::vector<int> v = {1, 2, 3, 4, 5};
int sum = std::accumulate(v.begin(), v.end(), 0);  // 15
int product = std::accumulate(v.begin(), v.end(), 1,
                              std::multiplies<>());  // 120

int cnt = std::count(v.begin(), v.end(), 3);  // 1

copy, remove y unique

remove no borra realmente — desplaza los elementos no coincidentes hacia adelante y devuelve un nuevo iterador end. Empárelo con .erase() para el idiom erase-remove. unique compacta similarmente duplicados consecutivos; ordene primero para desduplicar completamente.

cpp
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> dest;
std::copy(v.begin(), v.end(), std::back_inserter(dest));

auto end = std::remove(v.begin(), v.end(), 3);  // erase-remove idiom
v.erase(end, v.end());

std::vector<int> u = {1, 1, 2, 2, 3};
u.erase(std::unique(u.begin(), u.end()), u.end());  // 1 2 3

min, max y clamp

min/max devuelven el menor/mayor de dos valores o una initializer list. minmax devuelve ambos como un pair. clamp (C++17) restringe un valor a un rango, reemplazando verificaciones manuales if/else — útil para validación de entrada y coordenadas de UI.

cpp
int a = 3, b = 7;
std::cout << std::max(a, b);   // 7
std::cout << std::min(a, b);   // 3

auto [mn, mx] = std::minmax({3, 1, 4, 1, 5});  // mn=1, mx=5

int score = 105;
int clamped = std::clamp(score, 0, 100);  // 100
11

Manejo de Errores

Excepciones: try/catch

Lance excepciones por valor, capture por const reference para evitar slicing. Capturar std::exception captura todas las excepciones estándar vía la clase base. Las excepciones son para casos excepcionales, no flujo de control normal — tienen overhead cuando se lanzan.

cpp
#include <stdexcept>
try {
    int x = 10, y = 0;
    if (y == 0) throw std::runtime_error("division by zero");
    std::cout << x / y;
} catch (const std::exception &e) {
    std::cerr << "Error: " << e.what();
}

Jerarquía de Excepciones Estándar

<stdexcept> proporciona tipos de excepción estándar. logic_error es para errores del programador (detectables antes de runtime); runtime_error para condiciones imprevistas de runtime. Derive excepciones personalizadas de std::runtime_error para que se integren con bloques catch estándar.

cpp
#include <stdexcept>
throw std::runtime_error("runtime error");
throw std::logic_error("logic error");
throw std::out_of_range("index out of range");
throw std::invalid_argument("bad argument");
throw std::bad_alloc();  // thrown by new on allocation failure

// std::exception is the base; .what() returns the message

Excepciones Personalizadas

Derive excepciones personalizadas de una base estándar para que se integren con catch(const std::exception&). Añada campos de contexto (rutas de archivo, códigos de error) que ayuden a depurar. Siempre pase el mensaje al constructor base para que what() funcione.

cpp
class FileError : public std::runtime_error {
public:
    FileError(const std::string &msg, const std::string &path)
        : std::runtime_error(msg + ": " + path), path_(path) {}
    const std::string &path() const { return path_; }
private:
    std::string path_;
};

try { throw FileError("not found", "data.txt"); }
catch (const FileError &e) {
    std::cerr << e.what() << " at " << e.path();
}

noexcept y Seguridad RAII

noexcept promete que una función no lanzará, habilitando optimizaciones del compilador. Si lanza, se llama std::terminate. RAII garantiza que los destructores se ejecuten durante el stack unwinding, así que los recursos se liberan incluso cuando las excepciones se propagan por la pila de llamadas.

cpp
void safe_op() noexcept {
    // guaranteed not to throw
}

void risky() {
    std::vector<int> v(1000);  // RAII: cleanup on exception
    throw std::runtime_error("oops");
    // v's destructor still runs during stack unwinding
}

Assertions

assert() verifica condiciones en builds de depuración; se elimina en release (cuando NDEBUG está definido) así que no lo use para verificaciones de producción. Úselo para invariantes internos que indican bugs. Para validación orientada al usuario, lance excepciones o devuelva códigos de error.

cpp
#include <cassert>
double sqrt_safe(double x) {
    assert(x >= 0 && "sqrt of negative");
    return std::sqrt(x);
}
// In release builds (NDEBUG defined), assert is removed
12

File I/O y Streams

Leer un Archivo

ifstream abre un archivo para lectura. Siempre verifique si la apertura tuvo éxito (!file evalúa verdadero en fallo). getline lee línea por línea incluyendo espacios. El destructor del stream cierra el archivo automáticamente (RAII), así que no se necesita close manual.

cpp
#include <fstream>
#include <string>
std::ifstream file("input.txt");
if (!file) { std::cerr << "cannot open"; return 1; }

std::string line;
while (std::getline(file, line)) {
    std::cout << line << "\n";
}

Escribir un Archivo

ofstream escribe a un archivo, truncando por defecto. Use std::ios::app para añadir, std::ios::binary para modo binario. El operador << funciona exactamente como std::cout. Haga flush con out.flush() o use std::endl (que también hace flush).

cpp
#include <fstream>
std::ofstream out("output.txt");
if (!out) return 1;

out << "Line 1\n";
out << "Value: " << 42 << "\n";

out.close();  // optional, destructor closes
// Append mode: std::ofstream("f.txt", std::ios::app);

String Streams

ostringstream construye cadenas desde tipos mixtos (como un buffer). istringstream parsea cadenas en valores tipados. Son más lentos que operaciones directas de cadena pero muy convenientes para serialización, construcción de URLs y parseo de tokens.

cpp
#include <sstream>
// Build a string from mixed types
std::ostringstream oss;
oss << "Name=" << "Alice" << "&Age=" << 30;
std::string url = oss.str();

// Parse tokens from a string
std::istringstream iss("10 20 30");
int a, b, c;
iss >> a >> b >> c;

Archivos Binarios

El modo binario evita traducción de nuevas líneas y es más compacto que texto. write/read toman char* y conteo de bytes — use reinterpret_cast para structs. Nota: los archivos binarios no son portables entre arquitecturas (endianness, padding de struct difieren).

cpp
#include <fstream>
struct Record { int id; double value; };

// Write
std::ofstream out("data.bin", std::ios::binary);
Record r{1, 3.14};
out.write(reinterpret_cast<char*>(&r), sizeof(r));

// Read
std::ifstream in("data.bin", std::ios::binary);
Record r2;
in.read(reinterpret_cast<char*>(&r2), sizeof(r2));

Salida Formateada (fmt de C++20)

std::format (C++20) trae cadenas de formato estilo Python a C++, reemplazando los manipuladores iomanip desordenados. Para código más antiguo, <iomanip> proporciona setprecision, setw, setfill. La biblioteca {fmt} es una alternativa popular pre-C++20 con la misma sintaxis.

cpp
#include <format>  // C++20
std::string s = std::format("Hello, {}! You are {}.", "Alice", 30);
std::cout << std::format("{:.2f}", 3.14159);  // 3.14
std::cout << std::format("{:>10}", "right");   // padded

// Pre-C++20: use iomanip
#include <iomanip>
std::cout << std::fixed << std::setprecision(2) << 3.14159;
13

Smart Pointers

unique_ptr - Ownership Exclusiva

unique_ptr es el smart pointer predeterminado — úselo cuando un owner es suficiente. Tiene overhead cero vs punteros raw. make_unique es preferido (exception-safe). No puede copiarse, solo moverse. Los custom deleters habilitan RAII para recursos C como FILE* o sockets.

cpp
#include <memory>
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1;  // ERROR: cannot copy
std::unique_ptr<int> p2 = std::move(p1);  // OK: transfer ownership
// p1 is now nullptr

// Custom deleter
auto deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> fp(fopen("f.txt", "r"), deleter);

shared_ptr - Ownership Compartida

shared_ptr usa reference counting — múltiples punteros pueden poseer el mismo objeto. El objeto se destruye cuando refcount llega a 0. make_shared es preferido (asignación única para objeto + bloque de control). Más pesado que unique_ptr debido a refcount atómico y bloque de control. Use cuando la ownership se comparte genuinamente.

cpp
#include <memory>
auto p1 = std::make_shared<int>(42);
auto p2 = p1;  // OK: both share ownership
std::cout << *p1 << " " << p1.use_count();  // 42 2

// Control block holds refcount + deleter + allocator
// Refcount is atomic (thread-safe), but object access is NOT
std::shared_ptr<int> p3{new int{10}};  // uses non-array new

// Thread-safe: refcount operations are atomic
// NOT thread-safe: accessing the pointed-to object

weak_ptr - Romper Ciclos

weak_ptr es un observador no-owning de un shared_ptr. No aumenta el refcount. Use lock() para obtener temporalmente un shared_ptr (devuelve null si el objeto fue destruido). Esencial para romper ciclos de referencia (p. ej., listas doblemente enlazadas, relaciones padre-hijo) que causarían fugas de memoria.

cpp
#include <memory>
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // weak to avoid cycle
};

auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a;  // weak_ptr from shared_ptr

// To use a weak_ptr, lock() into a shared_ptr
if (auto locked = b->prev.lock()) {
    std::cout << "prev exists";
} else {
    std::cout << "prev expired";
}

Smart Pointers con Arrays

Los smart pointers pueden gestionar arrays. unique_ptr<T[]> proporciona operator[] y delete[] correcto. shared_ptr<T[]> se soporta desde C++17. Sin embargo, std::vector o std::array son casi siempre mejores — más seguros, más ergonómicos y auto-documentados. Use smart array pointers solo al interfazar con APIs legacy.

cpp
#include <memory>
// C++17: shared_ptr supports arrays
std::shared_ptr<int[]> arr1(new int[10]);
arr1[0] = 42;

// unique_ptr with arrays (partial specialization exists)
std::unique_ptr<int[]> arr2 = std::make_unique<int[]>(10);
arr2[5] = 100;

// Prefer std::array or std::vector over raw arrays
#include <vector>
std::vector<int> v(10);  // better choice

enable_shared_from_this

Cuando un objeto necesita devolver un shared_ptr a sí mismo, enable_shared_from_this proporciona shared_from_this() seguro. Llamar a shared_ptr<T>(this) directamente crearía un segundo bloque de control, provocando un double-free. El objeto ya debe estar gestionado por un shared_ptr, o shared_from_this() lanza bad_weak_ptr.

cpp
#include <memory>
class Widget : public std::enable_shared_from_this<Widget> {
public:
    std::shared_ptr<Widget> getPtr() {
        return shared_from_this();  // safe
        // return shared_ptr<Widget>(this);  // BAD: double delete
    }
};

auto w = std::make_shared<Widget>();
auto w2 = w->getPtr();  // shares ownership, refcount = 2
14

Semántica de Movimiento y Referencias Rvalue

Lvalues, Rvalues y Referencias

Los lvalues tienen identidad y persisten más allá de una sola expresión (objetos nombrados). Los rvalues son valores temporales o literales. T& se enlaza a lvalues, T&& se enlaza a rvalues. const T& es especial: se enlaza a ambos. Entender esta distinción es la base de la semántica de movimiento.

cpp
int x = 10;          // x is an lvalue
int& lref = x;       // lvalue reference
int&& rref = 20;     // rvalue reference (binds to temporary)
// int& bad = 20;    // ERROR: can't bind lref to rvalue
const int& cref = 20;  // OK: const lref binds to rvalue

int y = x + 5;       // (x + 5) is an rvalue (prvalue)
std::string s1 = "hi";
std::string&& rr = std::move(s1);  // rr is named rvalue

std::move y Constructores de Movimiento

std::move no mueve nada: hace un cast a rvalue, permitiendo que se seleccione el constructor/asignación de movimiento. Las operaciones de movimiento deberían ser noexcept para que los contenedores puedan usarlas durante la reasignación (de lo contrario, recurren a la copia por seguridad de excepciones). Después de un movimiento, el objeto origen queda en un estado válido pero no especificado.

cpp
class Buffer {
    int* data;
    size_t size;
public:
    // Move constructor: steal resources
    Buffer(Buffer&& other) noexcept
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    // Move assignment
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data; size = other.size;
            other.data = nullptr; other.size = 0;
        }
        return *this;
    }
};

Buffer a(100);
Buffer b = std::move(a);  // calls move ctor, a is now empty

Perfect Forwarding

El perfect forwarding pasa argumentos a otra función preservando su categoría de valor (lvalue vs rvalue). T&& en un contexto deducido es una 'forwarding reference' (no una rvalue reference). std::forward<T> hace un cast condicional: lvalue si T es T&, rvalue si T es T&&. Esencial para funciones fábrica y wrappers.

cpp
#include <utility>
template <typename T, typename Arg>
auto make_unique(Arg&& arg) {
    return std::unique_ptr<T>(new T(std::forward<Arg>(arg)));
}

template <typename... Args>
void log(Args&&... args) {
    // std::forward preserves value category
    log_impl(std::forward<Args>(args)...);
}

std::string s = "hello";
log(s);              // Arg = std::string&  (lvalue)
log(std::string());  // Arg = std::string&& (rvalue)

Regla de Cinco / Regla de Cero

Regla de Cinco: si una clase gestiona un recurso, debes definir destructor, constructor de copia, asignación de copia, constructor de movimiento y asignación de movimiento. Regla de Cero: prefiere componer tipos RAII (vector, string, smart pointers) para que los miembros especiales generados por el compilador sean correctos. Esto elimina la gestión manual de recursos propensa a errores.

cpp
// Rule of Five: if you define any of these, define all 5
class Resource {
    int* data;
public:
    Resource(size_t n) : data(new int[n]) {}
    ~Resource() { delete[] data; }
    Resource(const Resource& o);             // copy ctor
    Resource& operator=(const Resource& o);  // copy assign
    Resource(Resource&& o) noexcept;         // move ctor
    Resource& operator=(Resource&& o) noexcept;  // move assign
};

// Rule of Zero: prefer to use RAII types
class Better {
    std::vector<int> data;  // handles everything
public:
    Better() = default;  // compiler-generated funcs are correct
};

Return Value Optimization (RVO/NRVO)

RVO/NRVO permite al compilador construir el valor de retorno directamente en el almacenamiento del llamador, evitando copias/movimientos por completo. C++17 hace obligatorio RVO para prvalues. Nunca escribas return std::move(local): inhibe NRVO y fuerza un movimiento (más lento). Simplemente devuelve el local por nombre y deja que el compilador optimice.

cpp
std::vector<int> makeVec() {
    std::vector<int> v;
    v.push_back(1); v.push_back(2);
    return v;  // NRVO: no copy, no move!
}

std::string makeStr() {
    return std::string("hello");  // RVO
}

// C++17 guarantees RVO (mandatory copy elision) for prvalues
std::vector<int> v = makeVec();  // constructed in place

// Don't std::move return values—it disables RVO!
std::vector<int> bad() {
    std::vector<int> v;
    return std::move(v);  // WORSE: forces move, blocks NRVO
}
15

Concurrencia (thread, mutex, async)

Fundamentos de std::thread

std::thread lanza un nuevo hilo del SO. DEBES llamar a join() (esperar) o detach() (dejarlo correr independientemente) antes de que el objeto thread sea destruido, o se llama a std::terminate. Los argumentos se pasan por valor por defecto: usa std::ref para referencias, std::move para tipos solo-movibles. Prefiere join a menos que tengas una razón clara para detach.

cpp
#include <thread>
#include <iostream>

void worker(int id) {
    std::cout << "Thread " << id << "\n";
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2([]{ std::cout << "Lambda thread\n"; });

    t1.join();   // wait for t1 to finish
    t2.join();   // must join or detach before destruction
    // t1.detach();  // runs independently (risky)

    // Hardware concurrency hint
    unsigned n = std::thread::hardware_concurrency();
}

Mutex y Lock Guard

Protege siempre los datos compartidos con un mutex. std::lock_guard es el lock RAII más simple: adquiere en la construcción, libera en la destrucción. std::scoped_lock (C++17) bloquea múltiples mutex de forma segura usando un algoritmo de evitación de deadlock. std::unique_lock ofrece más flexibilidad (lock/unlock manual, bloqueo diferido) para usar con condition variables.

cpp
#include <mutex>
#include <vector>

std::mutex mtx;
std::vector<int> shared;

void safePush(int val) {
    // RAII lock: unlocks even if exception thrown
    std::lock_guard<std::mutex> lock(mtx);
    shared.push_back(val);
}  // lock released here

// std::scoped_lock (C++17) locks multiple mutexes deadlock-free
std::mutex m1, m2;
void transfer() {
    std::scoped_lock lock(m1, m2);  // atomic
}

// std::unique_lock: lockable/unlockable, movable
std::unique_lock<std::mutex> ul(mtx);
ul.unlock();
ul.lock();

Condition Variables

condition_variable permite a los hilos esperar una condición. Usa siempre un predicado con wait() para manejar spurious wakeups. El mutex debe estar retenido por un unique_lock al llamar a wait(), el cual lo libera mientras espera y lo readquiere antes de retornar. notify_one despierta a un waiter, notify_all despierta a todos. Este patrón implementa colas thread-safe y pipelines productor-consumidor.

cpp
#include <condition_variable>
#include <queue>
#include <thread>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> tasks;

void producer() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        tasks.push(42);
    }
    cv.notify_one();  // wake one waiting consumer
}

void consumer() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return !tasks.empty(); });  // predicate prevents spurious wakeup
    int task = tasks.front();
    tasks.pop();
}

std::async y Futures

std::async es una forma de alto nivel para ejecutar tareas asíncronamente, devolviendo un future. std::launch::async fuerza un nuevo hilo; std::launch::deferred se ejecuta perezosamente en get(). La política por defecto puede elegir cualquiera: sé explícito para un comportamiento predecible. Para más control, usa pares std::promise/future. Llama siempre a get() en un future antes de su destrucción, o el destructor podría bloquearse.

cpp
#include <future>
#include <iostream>

int slowComputation() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
}

// async policy: may run in new thread or deferred
auto fut = std::async(std::launch::async, slowComputation);
// do other work...
int result = fut.get();  // blocks until ready

// std::launch::async: definitely new thread
// std::launch::deferred: lazy, runs on get()
// std::launch::async | deferred: implementation chooses

// std::promise for manual control
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread([&p]{ p.set_value(10); }).detach();
f.get();  // 10

Operaciones Atómicas

std::atomic proporciona operaciones thread-safe sin locks para tipos primitivos. Más pesado que un int plano pero mucho más ligero que un mutex para contadores/flags simples. El ordenamiento de memoria afecta la visibilidad: relaxed (sin orden), acquire/release (par para sincronización), seq_cst (por defecto, el más fuerte). Usa atomics para contadores/flags; usa mutex para secciones críticas complejas.

cpp
#include <atomic>
#include <iostream>

std::atomic<int> counter{0};
std::atomic<bool> ready{false};

void worker() {
    while (!ready.load(std::memory_order_acquire));
    counter.fetch_add(1, std::memory_order_relaxed);
}

// Compare-and-swap (CAS) loop
std::atomic<int> val{0};
int expected = 0;
bool success = val.compare_exchange_weak(
    expected, 1,
    std::memory_order_acq_rel);

// Atomic is lock-free for most primitive types
static_assert(std::atomic<int>::is_always_lock_free);

// std::atomic_flag: guaranteed lock-free, for spinlocks
std::atomic_flag spin = ATOMIC_FLAG_INIT;
while (spin.test_and_set(std::memory_order_acquire));  // acquire
spin.clear(std::memory_order_release);  // release
16

Template Metaprogramming

Especialización de Templates

La especialización de templates proporciona implementaciones personalizadas para tipos específicos. La especialización completa fija todos los parámetros del template. La especialización parcial (solo para class templates) especializa algunos parámetros manteniendo otros genéricos. Se usa mucho en type traits, std::vector<bool>, y en la optimización para tipos conocidos.

cpp
// Primary template
template <typename T>
struct TypeName {
    static const char* get() { return "unknown"; }
};

// Full specialization for int
template <>
struct TypeName<int> {
    static const char* get() { return "int"; }
};

// Full specialization for const char*
template <>
struct TypeName<const char*> {
    static const char* get() { return "string"; }
};

std::cout << TypeName<int>::get();           // "int"
std::cout << TypeName<double>::get();        // "unknown"
std::cout << TypeName<const char*>::get();   // "string"

SFINAE y enable_if

SFINAE (Substitution Failure Is Not An Error) permite habilitar/deshabilitar sobrecargas de template basándose en propiedades de tipo. std::enable_if define condicionalmente un tipo. Cuando la sustitución falla, la sobrecarga se elimina silenciosamente en lugar de causar un error. if constexpr de C++17 y los concepts de C++20 a menudo reemplazan SFINAE con sintaxis más limpia.

cpp
#include <type_traits>

// SFINAE: Substitution Failure Is Not An Error
// Only enable this overload if T is integral
template <typename T,
          typename = std::enable_if_t<std::is_integral_v<T>>>
void process(T x) { std::cout << "integral\n"; }

// Only enable if T is floating point
template <typename T,
          std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
void process(T x) { std::cout << "floating\n"; }

process(10);    // integral
process(3.14);  // floating

// void_t trick (C++17) for detecting member existence
template <typename T, typename = void>
struct has_size : std::false_type {};
template <typename T>
struct has_size<T, std::void_t<decltype(std::declval<T>().size())>>
    : std::true_type {};

if constexpr (C++17)

if constexpr evalúa la condición en tiempo de compilación y descarta la rama falsa por completo (ni siquiera se verifica el tipo). Esto reemplaza muchos patrones SFINAE con código mucho más limpio. Particularmente útil para recursión de templates (el caso base termina la recursión) y para ramificar sobre type traits sin errores de instanciación.

cpp
#include <type_traits>
template <typename T>
auto getValue(T t) {
    if constexpr (std::is_pointer_v<T>) {
        return *t;  // only compiled if T is a pointer
    } else if constexpr (std::is_integral_v<T>) {
        return t * 2;
    } else {
        return t;
    }
}

int x = 5;
int* p = &x;
getValue(x);   // 10
getValue(p);   // 5
getValue(3.14);  // 3.14

// Compile-time recursion with termination
template <int N>
constexpr int factorial() {
    if constexpr (N <= 1) return 1;
    else return N * factorial<N - 1>();
}

Variadic Templates y Fold Expressions

Los variadic templates aceptan cualquier número de argumentos vía parameter packs (typename... Args). Las fold expressions de C++17 aplican un operador a todos los elementos del pack: unary fold (... op pack), binary fold (init op ... op pack). Antes de C++17, se necesitaba recursión con un caso base. Los variadic templates potencian std::make_unique, std::tuple, y funciones tipo printf.

cpp
#include <iostream>

// Parameter pack
template <typename... Args>
void print(Args... args) {
    // C++17 fold expression
    ((std::cout << args << " "), ...);
    std::cout << "\n";
}

print(1, "hello", 3.14, 'x');  // 1 hello 3.14 x

// Sum with fold
template <typename... T>
auto sum(T... args) {
    return (args + ...);  // binary fold: ((a+b)+c)+d
}

// Sum with initial value
template <typename... T>
auto sumFrom0(T... args) {
    return (0 + ... + args);  // left fold with init
}

// Base case recursion (pre-C++17)
template <typename T>
void printOne(T t) { std::cout << t; }

Concepts (C++20)

Los concepts (C++20) reemplazan SFINAE con restricciones legibles que revelan la intención. Producen mensajes de error mucho mejores que SFINAE. Define concepts con concept Name = constraint;. Úsalos en parámetros de template, cláusulas requires, o templates abreviados (auto con concept). La biblioteca estándar proporciona muchos concepts útiles en <concepts>.

cpp
#include <concepts>

// Define a concept
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

// Use as constraint
template <Numeric T>
T add(T a, T b) { return a + b; }

// Requires clause
template <typename T>
requires requires(T t) { t.size(); }
auto getSize(const T& t) { return t.size(); }

// Concepts in abbreviated function templates
void process(std::integral auto x) { /* ... */ }
void process(std::ranges::range auto& r) { /* ... */ }

// Standard concepts: integral, floating_point, equality_comparable,
// totally_ordered, copyable, movable, default_initializable, etc.
17

Expresiones Lambda en Profundidad

Modos de Captura

Los lambdas capturan variables del ámbito envolvente. [=] captura todo por valor, [&] por referencia: conveniente pero propenso a errores (referencias colgantes, capturas no intencionadas). Prefiere capturas explícitas [x, &y] para mayor claridad. Las init captures [name = expr] (C++14) permiten renombrar, mover y computar valores capturados. Captura [&] con cuidado: los lambdas que sobreviven al ámbito causan referencias colgantes.

cpp
int x = 10, y = 20;
std::vector<int> v;

auto a = [] { /* no capture */ };
auto b = [x] { return x; };           // capture x by value
auto c = [&x] { x = 100; };           // capture x by reference
auto d = [=] { return x + y; };       // capture all by value
auto e = [&] { x = 1; y = 2; };       // capture all by reference
auto f = [x, &y] { return x + y; };   // mixed
auto g = [=, &x] { x = 1; return y; }; // default value, x by ref
auto h = [this] { return member; };   // capture this (C++17: *this)
auto i = [x = x + 5] { return x; };   // init capture (C++14)

// Best practice: capture only what you need, explicitly

Lambdas Genéricos (C++14)

Los lambdas genéricos usan parámetros auto (C++14) o parámetros de template explícitos (C++20). Son esencialmente sobrecargas de operator() template generadas por el compilador. Los template lambdas de C++20 permiten acceder al parámetro de tipo T directamente. Los lambdas recursivos necesitan std::function (o deducing this de C++23) porque un lambda auto plano no puede referirse a sí mismo por nombre antes de que su tipo sea conocido.

cpp
// auto parameters (C++14)
auto add = [](auto a, auto b) { return a + b; };
add(1, 2);        // int
add(1.5, 2.5);    // double
add(std::string("a"), std::string("b"));

// Template lambdas (C++20)
auto getSize = []<typename T>(const std::vector<T>& v) {
    return v.size();
};

// Perfect forwarding in lambdas (C++20)
auto wrapper = []<typename T>(T&& arg) {
    return process(std::forward<T>(arg));
};

// Recursive lambda with std::function
#include <functional>
std::function<int(int)> fib = [&](int n) {
    return n < 2 ? n : fib(n - 1) + fib(n - 2);
};

Lambdas Mutable y con Estado

Por defecto, las variables capturadas por valor en un lambda son const: el operator() de llamada es const. La palabra clave mutable elimina esta constness, permitiendo la modificación de variables capturadas por valor (modificando la copia, no el original). Esto habilita lambdas con estado como contadores. Cada copia de un lambda tiene su propio estado capturado. Usa std::function para almacenar lambdas de diferentes tipos en contenedores.

cpp
#include <functional>
int counter = 0;

// Without mutable, value-captured vars are const
auto inc = [counter]() mutable {
    return ++counter;  // modifies the captured copy
};
inc(); inc();
std::cout << counter;  // still 0 (original unchanged)

// Stateful counter with std::function
auto makeCounter(int start) {
    return [count = start]() mutable { return ++count; };
}
auto c = makeCounter(10);
c(); c();  // 11, 12

// Lambda as callback storage
std::vector<std::function<void()>> callbacks;
callbacks.push_back([&]{ std::cout << "click"; });

Lambdas con Algoritmos STL

Los lambdas brillan como predicados/comparadores para algoritmos STL. Reemplazan el viejo enfoque de functors (objetos función) con código inline y legible. Usos comunes: comparadores para sort, predicados para find_if/remove_if/copy_if, transformaciones para transform. Los ranges de C++20 hacen esto aún más limpio con el operador pipe para transformaciones componibles.

cpp
#include <algorithm>
#include <vector>

std::vector<int> v = {5, 2, 8, 1, 9, 3};

// Sort descending
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;
});

// Find first even
auto it = std::find_if(v.begin(), v.end(),
    [](int x) { return x % 2 == 0; });

// Remove odds (erase-remove idiom)
v.erase(std::remove_if(v.begin(), v.end(),
    [](int x) { return x % 2 != 0; }), v.end());

// Transform
std::transform(v.begin(), v.end(), v.begin(),
    [](int x) { return x * x; });

// C++20 ranges: cleaner
auto evens = v | std::views::filter([](int x){ return x%2==0; })
               | std::views::transform([](int x){ return x*2; });

Funciones de Orden Superior

Los lambdas habilitan patrones de programación funcional en C++. Las funciones pueden devolver lambdas (closures) o aceptarlos como parámetros. std::function envuelve cualquier callable para almacenamiento type-erased. La composición de funciones construye pipelines encadenando lambdas. Este estilo es potente para callbacks, event handlers y pipelines de transformación de datos, aunque los enfoques basados en templates evitan el overhead de std::function.

cpp
#include <functional>
// Function returning a function
auto makeMultiplier(int factor) {
    return [factor](int x) { return x * factor; };
}
auto doubleIt = makeMultiplier(2);
auto tripleIt = makeMultiplier(3);
doubleIt(5);  // 10
tripleIt(5);  // 15

// Function taking a function
template <typename F>
void applyTwice(int x, F f) {
    std::cout << f(f(x));
}
applyTwice(5, [](int x){ return x + 3; });  // 11

// Composition
auto compose = [](auto f, auto g) {
    return [f, g](auto x) { return f(g(x)); };
};
auto addOne = [](int x){ return x + 1; };
auto square = [](int x){ return x * x; };
auto sqThenAdd = compose(addOne, square);
sqThenAdd(3);  // 10
18

Namespaces y Modules

Fundamentos de Namespace

Los namespaces previenen colisiones de nombres en proyectos grandes. Pueden anidarse y aliasarse para conveniencia. Los namespaces anónimos dan a los símbolos linkage interno (visibles solo en la unidad de traducción actual), reemplazando la palabra clave static estilo C para este propósito. Evita using namespace en headers: contamina cada archivo que lo incluye.

cpp
namespace math {
    double pi = 3.14159;
    double square(double x) { return x * x; }

    namespace geometry {  // nested
        double circleArea(double r) { return pi * square(r); }
    }
}

// Usage
double a = math::square(5);
double area = math::geometry::circleArea(2.0);

// Namespace alias
namespace geo = math::geometry;
geo::circleArea(1.0);

// Anonymous namespace: internal linkage (like 'static')
namespace {
    int internalVar = 42;  // only visible in this file
}

Declaraciones y Directivas using

La declaración using (using std::cout) importa un solo nombre: segura y recomendada. La directiva using (using namespace std) importa todo: conveniente pero arriesgada, especialmente en headers (causa conflictos de nombres y ambigüedad). Mantén las directivas using en un ámbito pequeño (archivos de función/implementación) y nunca en headers. using enum de C++20 importa todos los enumeradores de un scoped enum.

cpp
#include <iostream>
#include <vector>

// using declaration: brings one name
using std::cout;
using std::vector;
cout << "hello";
vector<int> v;

// using directive: brings entire namespace
using namespace std;  // brings ALL of std
// AVOID in headers! Pollutes global namespace.

// Namespace-scoped using (safe)
namespace mylib {
    using std::string;  // only affects mylib
    using std::vector;
    string s;
}

// C++20: using enum
enum class Color { Red, Green, Blue };
void print() {
    using enum Color;
    auto c = Red;  // no Color:: needed here
}

Argument-Dependent Lookup (ADL)

ADL (Koenig lookup) encuentra free functions basándose en los namespaces de sus argumentos. Por esto std::cout << x funciona sin std::operator<<: el compilador busca en el namespace de x. ADL es esencial para sobrecargas de operadores y customization points como swap. El patrón 'using std::swap; swap(a,b);' permite que los tipos de usuario proporcionen swaps optimizados mientras recurren a std::swap.

cpp
namespace mylib {
    struct Widget {
        int value;
    };
    // Operator overload found via ADL
    std::ostream& operator<<(std::ostream& os, const Widget& w) {
        return os << "Widget(" << w.value << ")";
    }

    void helper(const Widget&) {}
}

int main() {
    mylib::Widget w{42};
    // ADL: finds operator<< in mylib without qualification
    std::cout << w;  // works! no mylib:: needed

    // ADL applies to free functions too
    helper(w);  // wait—needs mylib::helper unless ADL applies
    mylib::helper(w);  // explicit
}

// swap is the classic ADL use case
namespace ns { struct X {};
    void swap(X&, X&) {}  // customized swap
}
void f(ns::X& a, ns::X& b) {
    using std::swap;
    swap(a, b);  // ADL picks ns::swap if available
}

Inline Namespaces (Versionado)

Los inline namespaces exponen sus miembros como si estuvieran en el namespace envolvente. Esto habilita el versionado de bibliotecas: haz la versión más nueva inline para que los usuarios la obtengan por defecto, mientras las versiones antiguas permanecen accesibles vía calificación explícita. También se usa para compatibilidad ABI y feature toggling. Cambiar qué namespace es inline desplaza la versión por defecto sin modificar el código de usuario.

cpp
// Inline namespace members are part of the enclosing namespace
namespace mylib {
    inline namespace v2 {
        struct Widget { int x, y, z; };  // newer version
        void process(Widget) {}
    }

    namespace v1 {
        struct Widget { int x, y; };  // older version
    }
}

// v2::Widget is accessible as mylib::Widget (inline)
mylib::Widget w;  // actually mylib::v2::Widget
mylib::process(w);

// Explicitly use v1 if needed
mylib::v1::Widget oldW;

// ABI versioning: change which namespace is inline
// to change default version without breaking old code

C++20 Modules

Los modules de C++20 reemplazan #include con un sistema más rápido y robusto. export module declara un módulo; export marca las declaraciones visibles. Los módulos se compilan una vez (no se reanalizan por unidad de traducción), mejorando drásticamente los tiempos de compilación. Evitan la contaminación de macros y los problemas de orden de headers. La adopción es gradual: el soporte de toolchains (CMake, build systems) aún está madurando a fecha de 2024.

cpp
// math.cppm (module interface unit)
export module math;

export double pi = 3.14159;

export double square(double x) {
    return x * x;
}

// Internal (not exported)
double internalHelper(double x) {
    return x * 2;
}

// main.cpp
import math;
import std;  // standard library module (C++23)

int main() {
    return square(pi);  // 9.87
    // internalHelper(3);  // ERROR: not exported
}

// Module partitions (sub-modules)
export module math:geometry;
export double circleArea(double r);
19

Preprocesador y Macros

Include Guards y #pragma once

Los include guards previenen que una cabecera se procese múltiples veces en una unidad de traducción, evitando errores de redefinición. #ifndef/#define/#endif es estándar y portable. #pragma once es más simple y evita colisiones de nombres de macro, pero técnicamente no es estándar (soportado por todos los compiladores principales). El código moderno a menudo usa #pragma once por simplicidad.

cpp
// Traditional include guard (header.h)
#ifndef MY_HEADER_H
#define MY_HEADER_H

// declarations here
class Widget { /* ... */ };

#endif  // MY_HEADER_H

// Alternative: #pragma once (non-standard but widely supported)
#pragma once

class Widget { /* ... */ };

// #pragma once pros: simpler, no risk of macro name collision
// Include guards pros: standard, works everywhere
// Both prevent multiple inclusion in a single TU

Macros tipo Función

Las macros tipo función son sustitución de texto. SIEMPRE pon entre paréntesis cada argumento y toda la expresión para evitar bugs de precedencia. El idiom do { ... } while (0) hace que una macro se comporte como una sola sentencia. Las macros no tienen verificación de tipos, ni ámbito, y pueden tener efectos secundarios (MAX(i++, j++) incrementa dos veces). Prefiere constexpr/inline/templates en C++ moderno.

cpp
// Basic macro
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))

int m = MAX(3, 5);      // ((3) > (5) ? (3) : (5))
int s = SQUARE(4);      // ((4) * (4))

// ALWAYS parenthesize arguments!
#define BAD(x) x * x
BAD(1 + 2);  // 1 + 2 * 1 + 2 = 5, not 9!

// Multi-line macro with do-while
#define LOG(msg) do { \
    std::cerr << __FILE__ << ":" << __LINE__ \
              << " " << msg << "\n"; \
} while (0)

// Variadic macro (C++11)
#define PRINT(...) printf(__VA_ARGS__)
PRINT("x=%d, y=%d\n", x, y);

Stringification y Concatenación

# (stringification) convierte un argumento de macro en un string literal. ## (token pasting) concatena dos tokens en uno. El truco de dos niveles STR/XSTR primero expande las macros y luego las stringifica. Estos operadores son esenciales para generación de código, macros de logging y X-macros. El C++ moderno a menudo los reemplaza con funciones constexpr, pero siguen siendo útiles para generación de strings/tokens en tiempo de compilación.

cpp
// # (stringification) turns a macro argument into a string literal
#define STR(x) #x
#define XSTR(x) STR(x)
const char* s1 = STR(hello world);  // "hello world"
const char* s2 = STR(42);           // "42"

// Two-level macro to expand macros before stringifying
#define VERSION 100
const char* v1 = STR(VERSION);   // "VERSION"
const char* v2 = XSTR(VERSION);  // "100"

// ## (token pasting) concatenates tokens
#define CONCAT(a, b) a ## b
#define MAKE_VAR(n) var_ ## n
int MAKE_VAR(1) = 10;  // int var_1 = 10;
int CONCAT(foo, bar) = 5;  // int foobar = 5;

// Useful for generating unique names
#define UNIQUE(prefix) prefix ## __LINE__
int UNIQUE(tmp_) = 0;  // int tmp_42 = 0; (if on line 42)

Compilación Condicional

La compilación condicional (#if, #ifdef, #ifndef, #elif, #else, #endif) incluye/excluye código en tiempo de compilación. Se usa para código específico de plataforma, builds de depuración y feature flags. defined(X) comprueba si X está definido (el valor es irrelevante). #error aborta la compilación con un mensaje. Prefiere constexpr if cuando sea posible: es type-safe y el código siempre se compila (detectando errores en todas las configuraciones).

cpp
#define DEBUG 1
#define PLATFORM "windows"

#if DEBUG
    std::cout << "Debug mode\n";
#endif

#ifdef DEBUG
    logDebug("entered function");
#endif

#ifndef NDEBUG
    assert(ptr != nullptr);
#endif

#if defined(_WIN32) || defined(_WIN64)
    #include <windows.h>
#elif defined(__linux__)
    #include <unistd.h>
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
#endif

// Check C++ standard
#if __cplusplus >= 202002L
    #define CPP20_OR_LATER
#elif __cplusplus >= 201703L
    #define CPP17_OR_LATER
#endif

// #error and #warning
#if !defined(VERSION)
    #error "VERSION must be defined"
#endif

Macros Predefinidas y __VA_OPT__

Las macros predefinidas proporcionan información en tiempo de compilación: __FILE__/__LINE__ para logging, __cplusplus para la versión del estándar, __func__ para nombres de función (es un identificador, no una macro). __VA_OPT__ (C++20) inserta condicionalmente una coma solo cuando __VA_ARGS__ no está vacío, arreglando el problema de la coma final en macros variádicas. __has_include (C++17) habilita la inclusión opcional de cabeceras para portabilidad.

cpp
// Standard predefined macros
std::cout << __FILE__;       // source file path
std::cout << __LINE__;       // current line number
std::cout << __DATE__;       // compilation date "Mmm dd yyyy"
std::cout << __TIME__;       // compilation time "hh:mm:ss"
std::cout << __cplusplus;    // C++ standard version
std::cout << __func__;       // current function name (not macro)

// Compiler-specific
#ifdef __GNUC__
    std::cout << __GNUC__ << "." << __GNUC_MINOR__;
#endif
#ifdef _MSC_VER
    std::cout << "MSVC " << _MSC_VER;
#endif

// __VA_OPT__ (C++20): expands to its arg if variadic has args
#define LOG(fmt, ...) \
    printf(fmt __VA_OPT__(,) __VA_ARGS__)
LOG("plain");              // printf("plain")
LOG("x=%d", x);            // printf("x=%d", x)

// __has_include (C++17): check if header exists
#if __has_include(<optional>)
    #include <optional>
#endif
20

Patrones de Diseño en C++

Singleton (Meyers' Singleton)

Singleton asegura que una clase tenga una instancia con acceso global. El Singleton de Meyers (variable local estática) es la implementación más limpia en C++: la inicialización thread-safe está garantizada desde C++11. Elimina las operaciones de copia para prevenir duplicación. Los singletons son controvertidos (estado global, difíciles de testear); prefiere dependency injection cuando sea factible. Úsalo cuando genuinamente se necesite una sola instancia (logger, config, interfaz de hardware).

cpp
class Logger {
public:
    static Logger& instance() {
        // Meyers' singleton: thread-safe in C++11+
        static Logger inst;
        return inst;
    }
    void log(const std::string& msg) {
        std::cout << "[LOG] " << msg << "\n";
    }
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
private:
    Logger() = default;
};

// Usage
Logger::instance().log("started");

// Avoid: lazy init with new + manual sync (error-prone)
// The static local is initialized once, thread-safely

RAII (Resource Acquisition Is Initialization)

RAII es el idiom más importante de C++: adquiere recursos en constructores, libera en destructores. Esto garantiza la limpieza incluso cuando se lanzan excepciones. Los smart pointers, std::lock_guard, std::fstream y std::vector usan todos RAII. El destructor se ejecuta durante el stack unwinding, haciendo el código exception-safe natural. RAII elimina el new/delete, lock/unlock, open/close manual: abrázalo en todas partes.

cpp
// RAII: resource tied to object lifetime
class FileHandle {
    FILE* fp;
public:
    explicit FileHandle(const char* path) : fp(fopen(path, "r")) {
        if (!fp) throw std::runtime_error("open failed");
    }
    ~FileHandle() { if (fp) fclose(fp); }
    FILE* get() { return fp; }
    // disable copy to prevent double-close
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    // allow move
    FileHandle(FileHandle&& o) noexcept : fp(o.fp) { o.fp = nullptr; }
};

{
    FileHandle f("data.txt");
    // use f.get()
}  // fclose called automatically, even on exception

Idiom Pimpl (Pointer to Implementation)

Pimpl (Pointer to Implementation) oculta los detalles de implementación detrás de un puntero. Beneficios: compilación más rápida (los miembros privados no aparecen en el header), verdadera estabilidad ABI (cambiar Impl no rompe el ABI), dependencias de include reducidas. El destructor debe definirse en el archivo .cpp porque unique_ptr<Impl> necesita que Impl esté completo. Común en diseño de bibliotecas para APIs estables.

cpp
// widget.h - public interface
class Widget {
public:
    Widget();
    ~Widget();  // must define in .cpp (unique_ptr needs complete type)
    Widget(Widget&&) noexcept;
    Widget& operator=(Widget&&) noexcept;
    void doSomething();
private:
    class Impl;  // forward declaration
    std::unique_ptr<Impl> pimpl;
};

// widget.cpp - implementation
#include "widget.h"
#include <vector>
class Widget::Impl {
public:
    std::vector<int> data;
    void doSomething() { /* ... */ }
};
Widget::Widget() : pimpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
Widget::Widget(Widget&&) noexcept = default;
void Widget::doSomething() { pimpl->doSomething(); }

Patrón Observer

Observer permite a los subjects notificar a los suscriptores de cambios sin acoplamiento. std::function facilita aceptar lambdas, funciones miembro y functors. Para código de producción, considera un mecanismo de cancelación de suscripción basado en tokens (devuelve un ID al suscribirse, úsalo para eliminar). Cuidado con las referencias colgantes si los observers capturan [&] y sobreviven al subject. Las signals/slots de Qt y los events de C# son implementaciones maduras.

cpp
#include <functional>
#include <vector>
#include <string>

class Subject {
    std::vector<std::function<void(const std::string&)>> observers;
public:
    void subscribe(std::function<void(const std::string&)> cb) {
        observers.push_back(cb);
    }
    void notify(const std::string& event) {
        for (auto& cb : observers) cb(event);
    }
};

// Usage
Subject s;
s.subscribe([](const std::string& e){ std::cout << "A: " << e; });
s.subscribe([](const std::string& e){ std::cout << "B: " << e; });
s.notify("hello");  // both callbacks invoked

// For removal, use a token/ID system or observer objects
// instead of std::function

CRTP (Curiously Recurring Template Pattern)

CRTP (Derived : Base<Derived>) consigue polimorfismo estático: la clase base personaliza el comportamiento vía el tipo derivado. Sin overhead de funciones virtuales. Se usa para añadir funcionalidad (Comparable, Iterable) a clases derivadas vía mixins. El downcast vía static_cast es seguro porque el template garantiza el tipo derivado. CRTP potencia std::enable_shared_from_this, std::iterator y muchos diseños basados en políticas.

cpp
// CRTP: class Derived : public Base<Derived>
template <typename Derived>
struct Comparable {
    bool operator==(const Derived& other) const {
        return static_cast<const Derived*>(this)->equalTo(other);
    }
    bool operator!=(const Derived& other) const {
        return !(*this == other);
    }
};

struct Point : Comparable<Point> {
    int x, y;
    bool equalTo(const Point& o) const { return x == o.x && y == o.y; }
};

Point a{1, 2}, b{1, 2};
a == b;  // true (uses Comparable::operator==)
a != b;  // false

// Static polymorphism (no virtual overhead)
template <typename T>
void draw(const T& shape) {
    static_cast<const T&>(shape).drawImpl();
}
21

Smart Pointers en Profundidad

unique_ptr

unique_ptr es el único propietario de su objeto. No puede copiarse, solo moverse. Se elimina automáticamente al salir del ámbito. make_unique es la forma preferida de crearlo. Overhead cero frente a punteros raw.

cpp
#include <memory>
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1;  // Error: cannot copy
std::unique_ptr<int> p3 = std::move(p1);  // OK: transfer ownership
// p1 is now nullptr

shared_ptr

shared_ptr permite múltiples propietarios vía reference counting. use_count() muestra el número de propietarios. Thread-safe para el contador pero no para el objeto. Usa make_shared para eficiencia (una sola asignación).

cpp
auto p1 = std::make_shared<int>(42);
auto p2 = p1;  // OK: shared ownership
std::cout << p1.use_count();  // 2
// Reference counting: deleted when count reaches 0
// Thread-safe for reference count, not for the object

weak_ptr

weak_ptr es una referencia no propietaria a shared_ptr. Previene referencias circulares (fugas de memoria). lock() intenta convertir a shared_ptr. Usa expired() para comprobar si el objeto aún existe. No afecta al reference count.

cpp
auto shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;  // Does not increase count
if (auto locked = weak.lock()) {  // Try to get shared_ptr
    std::cout << *locked;  // Use the object
} else {
    std::cout << "Object deleted";
}

Custom Deleter

Los custom deleters permiten a unique_ptr gestionar recursos no de memoria como file handles, sockets y APIs de C. El tipo deleter es parte del tipo unique_ptr. Útil para RAII con bibliotecas de C.

cpp
std::unique_ptr<FILE, decltype(&fclose)> file(fopen("test.txt", "r"), fclose);
// Or with lambda
auto deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> file2(fopen("test.txt", "r"), deleter);

enable_shared_from_this

enable_shared_from_this permite a un objeto obtener de forma segura un shared_ptr a sí mismo. Llamar a shared_ptr(this) directamente crearía un segundo reference count, provocando double deletion. Hereda de enable_shared_from_this y usa shared_from_this().

cpp
class Node : public std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getPtr() {
        return shared_from_this();  // Safe
        // return std::shared_ptr<Node>(this);  // BUG: double delete
    }
};
22

Semántica de Movimiento en Profundidad

Constructor de Movimiento

Los constructores de movimiento roban recursos en lugar de copiar. noexcept es importante: los contenedores STL solo mueven (no copian) si el movimiento es noexcept. Deja siempre el objeto movido-from en un estado válido.

cpp
class Buffer {
    int* data; size_t size;
public:
    Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr; other.size = 0;  // Leave in valid state
    }
    Buffer& operator=(Buffer&& other) noexcept {
        delete[] data;
        data = other.data; size = other.size;
        other.data = nullptr; other.size = 0;
        return *this;
    }
};

std::move

std::move es un cast a rvalue reference. No realiza el movimiento en sí mismo. El constructor de movimiento o el operador de asignación hace el trabajo real. Después de std::move, el objeto origen queda en un estado válido pero no especificado.

cpp
std::string s1 = "Hello";
std::string s2 = std::move(s1);  // s1 is now empty
// std::move does not move anything!
// It casts to an rvalue reference, enabling move
// The actual move happens in the constructor/assignment

Perfect Forwarding

El perfect forwarding preserva la categoría de valor de los argumentos. T&& en un template es una forwarding reference, que se enlaza tanto a lvalues como a rvalues. std::forward<T> hace el cast de vuelta a la categoría original. Esencial para funciones fábrica y wrappers.

cpp
template<typename T, typename Arg>
auto make(Arg&& arg) {
    return std::make_shared<T>(std::forward<Arg>(arg));
}
// std::forward preserves value category:
// lvalue stays lvalue, rvalue stays rvalue
// Arg&& is a forwarding reference (not rvalue reference)

RVO y NRVO

RVO (Return Value Optimization) y NRVO (Named RVO) eliminan copias construyendo el objeto in place. Los compiladores realizan esto automáticamente. Usar std::move en un valor de retorno local previene NRVO y puede empeorar el rendimiento. Confía en el compilador.

cpp
std::string create() {
    std::string s = "Hello";
    return s;  // NRVO: no copy, no move
}
std::string s = create();  // RVO: no copy
// With -O2, compilers eliminate the copy/move entirely
// Do NOT use std::move on return of local variable!

Regla de Cinco

Regla de Cinco: si defines cualquiera de destructor, constructor/asignación de copia, o constructor/asignación de movimiento, define los cinco. Esto asegura una gestión correcta de recursos. Se prefiere la Regla de Cero: usa tipos RAII (smart pointers, vectors) para evitar la gestión manual.

cpp
class Resource {
public:
    Resource();  // Constructor
    ~Resource();  // Destructor
    Resource(const Resource&);  // Copy constructor
    Resource& operator=(const Resource&);  // Copy assignment
    Resource(Resource&&) noexcept;  // Move constructor
    Resource& operator=(Resource&&) noexcept;  // Move assignment
};
23

Templates en Profundidad

Variadic Templates

Los variadic templates aceptan cualquier número de argumentos. Las fold expressions (C++17) simplifican el unpacking. El enfoque recursivo funciona en C++11. sizeof...(args) da el conteo. Se usan en tuple, make_shared y reemplazos de printf.

cpp
template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << '\n';  // C++17 fold expression
}
print(1, "hello", 3.14, 'a');  // 1hello3.14a
// Recursive unpacking (pre-C++17)
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first;
    print(rest...);
}

SFINAE

SFINAE (Substitution Failure Is Not An Error) elimina del consideración las sobrecargas inválidas. enable_if añade una condición al template. Si la condición es false, la especialización se ignora. if constexpr de C++17 es a menudo más limpio.

cpp
template<typename T,
         typename = std::enable_if_t<std::is_integral_v<T>>>
void process(T value) { /* integer version */ }
template<typename T,
         typename = std::enable_if_t<std::is_floating_point_v<T>>,
         typename = void>  // Different signature
void process(T value) { /* float version */ }

if constexpr

if constexpr (C++17) evalúa condiciones en tiempo de compilación. Solo la rama true se compila, la otra se descarta. Mucho más limpio que SFINAE para compilación condicional. Funciona con deducción de tipo de retorno auto.

cpp
template<typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>)
        return *t;
    else
        return t;
}
// Only the matching branch is compiled
// No SFINAE needed

Concepts (C++20)

Los concepts (C++20) restringen los parámetros de template con sintaxis legible. Proporcionan mejores mensajes de error que SFINAE. Usa concepts existentes (integral, floating_point) o define los tuyos propios. Los concepts pueden combinarse con && y ||.

cpp
template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;
template<Number T>
T add(T a, T b) { return a + b; }
// Or: requires clause
template<typename T> requires Number<T>
T multiply(T a, T b) { return a * b; }

Especialización de Templates

La especialización completa proporciona una implementación completa para un tipo específico. La especialización parcial personaliza para una categoría (p. ej., todos los punteros). El template primario debe declararse primero. Las especializaciones deben coincidir con la interfaz.

cpp
template<typename T>
class Vector { /* general implementation */ };
template<>
class Vector<bool> {  // Full specialization
    // Bit-packed implementation
};
template<typename T>
class Vector<T*> {  // Partial specialization for pointers
    // Pointer-specific implementation
};
24

Algoritmos STL en Profundidad

sort y stable_sort

sort es O(n log n), no estable. stable_sort preserva el orden relativo de elementos iguales. Usa un comparador para ordenamiento personalizado. Los ranges de C++20 permiten: std::ranges::sort(v, {}, &last_digit).

cpp
std::vector<int> v = {3, 1, 4, 1, 5, 9};
std::sort(v.begin(), v.end());  // 1,1,3,4,5,9
std::sort(v.begin(), v.end(), std::greater<>());  // Descending
// Custom comparator
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a % 10 < b % 10;  // Sort by last digit
});

transform y accumulate

transform aplica una función a cada elemento. accumulate pliega elementos con una operación (+ por defecto). El valor inicial determina el tipo. Usa std::reduce (C++17) para reducción paralela.

cpp
std::vector<int> v = {1, 2, 3, 4};
std::vector<int> squared(v.size());
std::transform(v.begin(), v.end(), squared.begin(),
    [](int x) { return x * x; });  // 1,4,9,16
int sum = std::accumulate(v.begin(), v.end(), 0);  // 10
int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<>());

find y count

find devuelve un iterador al primer match o end(). count devuelve el número de coincidencias. find_if usa un predicado. Todos son O(n). Para rangos ordenados, usa binary_search, lower_bound, upper_bound (O(log n)).

cpp
std::vector<int> v = {1, 2, 3, 2, 1};
auto it = std::find(v.begin(), v.end(), 2);  // First 2
size_t cnt = std::count(v.begin(), v.end(), 2);  // 2
auto it2 = std::find_if(v.begin(), v.end(),
    [](int x) { return x > 2; });  // First > 2

copy y remove

copy_if copia los elementos que coinciden. back_inserter añade al destino. remove no elimina realmente: desplaza los elementos no coincidentes hacia adelante y devuelve un nuevo end. erase elimina los sobrantes. C++20 añade std::erase para contenedores.

cpp
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> evens;
std::copy_if(v.begin(), v.end(), std::back_inserter(evens),
    [](int x) { return x % 2 == 0; });  // 2,4
// Remove-erase idiom
v.erase(std::remove(v.begin(), v.end(), 3), v.end());
// C++20: std::erase(v, 3);

minmax y clamp

minmax devuelve un par de (min, max) en una sola pasada. clamp restringe un valor a un rango. min/max aceptan initializer lists o dos valores. Todos aceptan comparadores personalizados. Los structured bindings (C++17) simplifican el resultado.

cpp
auto [min, max] = std::minmax({3, 1, 4, 1, 5});  // C++17
int value = std::clamp(15, 0, 10);  // 10 (clamped to max)
int m = std::min({1, 2, 3});
int m2 = std::max({1, 2, 3}, [](int a, int b) { return a < b; });
25

Concurrencia

std::thread

std::thread crea hilos del SO. join() espera la finalización (bloqueante). detach() se ejecuta independientemente (puede sobrevivir al creador). Un thread sin ninguno de los dos causa std::terminate. Pasa argumentos por valor o usa std::ref para referencias.

cpp
#include <thread>
void task(int n) { /* ... */ }
std::thread t1(task, 42);
std::thread t2([]() { /* lambda */ });
t1.join();  // Wait for completion
t2.detach();  // Run independently
// Always join or detach before destruction

std::mutex

lock_guard es RAII simple: bloquea en la construcción, desbloquea en la destrucción. unique_lock es más flexible: puede desbloquear/rebloquear, se usa con condition variables. Nunca desbloquees manualmente con lock_guard. Usa std::scoped_lock para múltiples mutex.

cpp
std::mutex mtx;
int shared = 0;
void increment() {
    std::lock_guard<std::mutex> lock(mtx);  // RAII
    ++shared;
}  // Auto-unlock
// std::unique_lock for conditional locking
std::unique_lock<std::mutex> ulock(mtx);
ulock.unlock();  // Manual unlock
ulock.lock();    // Re-lock

std::async y futures

std::async ejecuta una función asíncronamente, devolviendo un future. get() bloquea y recupera el resultado. launch::async fuerza un nuevo hilo. launch::deferred se ejecuta sincrónicamente en get(). El por defecto puede ser cualquiera. Las excepciones se propagan a través de get().

cpp
#include <future>
std::future<int> f = std::async(std::launch::async, []() {
    return 42;
});
int result = f.get();  // Blocks until ready
// std::launch::async: always new thread
// std::launch::deferred: lazy, runs on get()

Condition Variable

condition_variable sincroniza hilos. wait libera el lock y bloquea hasta ser notificado. El predicado maneja spurious wakeups. notify_one despierta a un waiter, notify_all despierta a todos. Modifica siempre el estado compartido bajo el mutex antes de notificar.

cpp
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
// Waiter
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []() { return ready; });  // Spurious wakeups handled
// Notifier
{
    std::lock_guard<std::mutex> lk(mtx);
    ready = true;
}
cv.notify_one();  // Or notify_all()

Operaciones Atómicas

atomic proporciona operaciones thread-safe sin locks. fetch_add/sub son read-modify-write. compare_exchange implementa CAS (compare-and-swap). memory_order_relaxed es el más rápido pero el más débil. seq_cst (por defecto) es el más fuerte. Usa atomics para contadores y flags simples.

cpp
#include <atomic>
std::atomic<int> counter{0};
counter++;  // Atomic increment
counter.fetch_add(1, std::memory_order_relaxed);
bool expected = false;
counter.compare_exchange_strong(expected, true);
// Memory orders: relaxed, acquire, release, seq_cst
26

Características de C++ Moderno

Structured Bindings

Los structured bindings (C++17) descomponen pairs, tuples y structs. auto& para referencias, auto para copias. Simplifica la iteración sobre maps. Funciona con cualquier tipo aggregate. Mucho más limpio que .first/.second.

cpp
std::pair p = {1, "hello"};
auto [num, str] = p;  // C++17
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
for (const auto& [key, value] : m) {
    std::cout << key << ": " << value;
}

std::optional

optional representa un valor que puede o no existir. Mejor que punteros o valores centinela. has_value() u operator bool comprueba. value() lanza si está vacío, value_or() proporciona un valor por defecto. Úsalo para funciones que pueden no producir un resultado.

cpp
#include <optional>
std::optional<int> find(bool found) {
    if (found) return 42;
    return std::nullopt;
}
auto result = find(true);
if (result) std::cout << *result;
// Or: result.value_or(0)

std::variant

variant es una union type-safe. Contiene uno de varios tipos. visit aplica un visitor (lambda overloaded). index() devuelve el índice del tipo actual. get_if<T> recupera de forma segura. Reemplaza unions y herencia para jerarquías de tipos cerradas.

cpp
#include <variant>
std::variant<int, std::string> v;
v = 42;
v = "hello";
std::visit([](auto&& arg) {
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>)
        std::cout << "int: " << arg;
    else if constexpr (std::is_same_v<T, std::string>)
        std::cout << "string: " << arg;
}, v);

Ranges (C++20)

Los ranges (C++20) proporcionan views componibles y perezosas. El operador | encadena operaciones. Sin contenedores intermedios. Las views son perezosas: la computación ocurre en la iteración. Mucho más legible que llamadas anidadas a algoritmos.

cpp
#include <ranges>
namespace rv = std::ranges::views;
auto result = std::vector{1, 2, 3, 4, 5}
    | rv::filter([](int x) { return x % 2 == 0; })
    | rv::transform([](int x) { return x * x; });
// Lazy evaluation: no computation until iterated
for (int x : result) std::cout << x;  // 4, 16

Coroutines (C++20)

Las coroutines (C++20) habilitan patrones async y generator. co_yield suspende y devuelve un valor. co_await espera a otra coroutine. co_return finaliza. El compilador transforma las coroutines en state machines. Necesita una implementación de tipo de retorno (Generator, Task).

cpp
#include <coroutine>
Generator<int> counter() {
    for (int i = 0; ; ++i)
        co_yield i;  // Suspend and yield
}
for (int x : counter()) {
    if (x > 5) break;
    std::cout << x;
}
// co_await: wait for async operation
// co_return: finish coroutine
27

Errores Comunes

Punteros Colgantes

Los punteros colgantes apuntan a memoria liberada. Acceder a ellos es undefined behavior. Usa smart pointers (unique_ptr, shared_ptr) para evitar la gestión manual de memoria. Si usas punteros raw, ponlos a nullptr después de delete. Usa herramientas como AddressSanitizer para detectarlos.

cpp
// BUG: dangling pointer
int* p = new int(42);
delete p;
std::cout << *p;  // Undefined behavior
// FIX: use smart pointers
auto p = std::make_unique<int>(42);
// Or set to nullptr after delete
int* p2 = new int(42);
delete p2; p2 = nullptr;

Invalidación de Iteradores

push_back en Vector puede reasignar memoria, invalidando todos los iteradores. erase invalida los iteradores en y después del punto. Los iteradores de list son estables excepto para los elementos borrados. Comprueba las reglas de invalidación de iteradores para cada contenedor. En caso de duda, usa índices.

cpp
std::vector<int> v = {1, 2, 3};
// BUG: iterator invalidated
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it == 2) v.push_back(4);  // May invalidate it
}
// FIX: use index
for (size_t i = 0; i < v.size(); ++i) {
    if (v[i] == 2) v.push_back(4);
}

Undefined Behavior

El undefined behavior (UB) permite al compilador hacer cualquier cosa. Variables sin inicializar, acceso fuera de límites, null dereference y signed overflow son todos UB. Los compiladores optimizan asumiendo que no hay UB, produciendo resultados sorprendentes. Usa -fsanitize=undefined para detectarlo.

cpp
int x;  // Uninitialized: UB to read
std::cout << x;  // UB
int arr[5];
arr[5] = 0;  // Out of bounds: UB
int* p = nullptr;
*p;  // Null dereference: UB
// Signed overflow: UB (unsigned wraps)
int big = INT_MAX + 1;  // UB

Slicing

El object slicing ocurre cuando un objeto derivado se copia a un tipo base. Las partes derivadas se pierden. Pasa por referencia o puntero para evitar el slicing. Las llamadas virtuales en un objeto sliced llaman a la versión base. Pasa siempre los tipos polimórficos por referencia/puntero.

cpp
class Base { public: virtual ~Base() {} };
class Derived : public Base { int extra; };
void take(Base b) { /* copies Base part only */ }
Derived d;
take(d);  // Sliced: extra lost
// FIX: pass by reference or pointer
void take(const Base& b) { /* full object */ }

Static Initialization

El orden de static initialization entre unidades de traducción es indefinido. Un archivo puede usar un static de otro archivo aún no inicializado. Arréglalo con statics function-local (Meyers Singleton): la inicialización es perezosa y thread-safe (C++11+). Accede vía llamada a función.

cpp
// Static initialization order fiasco
// File1.cpp
int x = computeX();  // May use y
// File2.cpp
int y = computeY();  // May use x
// Order across files is undefined!
// FIX: function-local static
int& getX() {
    static int x = computeX();  // Lazy, thread-safe
    return x;
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.