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.
#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.
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.
#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.
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 6Conversió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.
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);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.
#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"); // 7Comparació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.
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 occurrenceStringstream
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.
#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=30Cadenas 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.
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.
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++ -> CNú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.
#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.
#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.
#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.5Nú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.
#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.
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
}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.
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.
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).
// 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 copyWhile 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.
int n = 5;
while (n > 0) {
std::cout << n-- << " ";
}
int x;
do {
std::cin >> x;
} while (x < 0); // runs at least onceBreak, 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.
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; }
}
}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.
#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).
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) == 32Sobrecarga 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.
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 versionExpresiones 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.
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.
#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); // 12Contenedores 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.
#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.
#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.
#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 >= 3array 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.
#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, 4tuple 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).
#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);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.
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); // 2Referencias 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.
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 nullunique_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.
#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 nullptrshared_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).
#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.
// 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 neededClases 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.
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.
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.
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.
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.
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)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.
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"); // explicitClass 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.
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.
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); // specializedVariadic 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.
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.14Concepts (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.
#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; }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.
#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.
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 8accumulate 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.
#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); // 1copy, 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.
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 3min, 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.
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); // 100Manejo 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.
#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.
#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 messageExcepciones 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.
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.
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.
#include <cassert>
double sqrt_safe(double x) {
assert(x >= 0 && "sqrt of negative");
return std::sqrt(x);
}
// In release builds (NDEBUG defined), assert is removedFile 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.
#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).
#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.
#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).
#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.
#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;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.
#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.
#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 objectweak_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.
#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.
#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 choiceenable_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.
#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 = 2Semá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.
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 rvaluestd::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.
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 emptyPerfect 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.
#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.
// 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.
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
}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.
#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.
#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.
#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.
#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(); // 10Operaciones 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.
#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); // releaseTemplate 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.
// 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.
#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.
#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.
#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>.
#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.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.
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, explicitlyLambdas 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.
// 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.
#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.
#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.
#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); // 10Namespaces 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.
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.
#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.
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.
// 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 codeC++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.
// 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);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.
// 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 TUMacros 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.
// 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.
// # (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).
#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"
#endifMacros 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.
// 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>
#endifPatrones 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).
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-safelyRAII (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.
// 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 exceptionIdiom 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.
// 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.
#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::functionCRTP (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.
// 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();
}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.
#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 nullptrshared_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).
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 objectweak_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.
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.
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().
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
}
};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.
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.
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/assignmentPerfect 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.
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.
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.
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
};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.
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.
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.
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 neededConcepts (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 ||.
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.
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
};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).
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.
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)).
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 > 2copy 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.
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.
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; });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.
#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 destructionstd::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.
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-lockstd::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().
#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.
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.
#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_cstCaracterí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.
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.
#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.
#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.
#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, 16Coroutines (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).
#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 coroutineErrores 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.
// 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.
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.
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; // UBSlicing
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.
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.
// 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;
}Fragmentos de C++ relacionados
Copy-paste ready code for common tasks.
Smart Pointers
unique_ptr, shared_ptr, weak_ptr.
RAII
Resource Acquisition Is Initialization.
Move Semantics
Referencias rvalue y move constructors.
Expresiones Lambda
Lambda y capturas.
Templates
Templates de función y de clase.
Contenedores STL
Operaciones comunes de contenedores.
Algoritmos STL
Funciones de algoritmos comunes.
Iterador
Tipos y uso de iteradores.
Manejo de Excepciones
try-catch y excepciones personalizadas.
Multihilo
thread, mutex, condition_variable.
File IO
Operaciones de lectura y escritura de archivos.
Strings
Operaciones de std::string.
Expresiones Regulares
Coincidencia y reemplazo con std::regex.
Deducción de Tipos
auto, decltype, deducción de templates.
constexpr
Constantes y cálculo en tiempo de compilación.
Was this helpful?