Skip to content

C++ Folha de referência

Linguagem de propósito geral com OOP, genéricos e controle de baixo nível.

01

Introdução

Hello World

Todo programa em C++ começa em main(). <iostream> fornece std::cout (saída padrão) e std::cin (entrada padrão). O prefixo std:: refere-se ao namespace padrão; using namespace std; pode evitá-lo, mas é desencorajado em headers porque polui o namespace global.

cpp
#include <iostream>

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

Variáveis & auto

auto deduz o tipo a partir do inicializador (C++11). Use auto quando o tipo for óbvio ou verboso (iteradores). const torna um valor imutável; constexpr avalia em tempo de compilação para verdadeiras constantes incorporadas no binário.

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 & Saída

std::getline lê uma linha completa incluindo espaços, enquanto std::cin >> para em whitespace. Misturá-los deixa uma nova linha no buffer; chame std::cin.ignore() entre getline e >> para descartá-la.

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";
}

Referências

Referências são aliases que devem ser inicializadas e não podem ser reatribuídas a outro objeto. Passagem por referência evita cópia e permite modificar a variável do chamador. Use const T& para parâmetros somente leitura a fim de evitar cópias caras.

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

Conversão de Tipos

Prefira static_cast a casts estilo C porque é explícito e verificado em tempo de compilação, tornando a intenção clara. std::stoi, std::stod convertem strings em números; std::to_string faz o inverso. Atenção a std::out_of_range em entrada inválida.

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

Strings

Básico de std::string

std::string gerencia sua própria memória e cresce conforme necessário. Diferente de arrays de char do C, você não gerencia o comprimento manualmente. .find() retorna std::string::npos (um valor enorme) quando a substring não é encontrada, então sempre 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

Comparação & Busca

A comparação é lexicográfica (ordem de dicionário). .find() busca para frente, .rfind() busca para trás. Ambos retornam std::string::npos se não encontrados, então sempre compare contra npos em vez de tratar o 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 faz a ponte entre strings e valores tipados, útil para construir strings formatadas (como um buffer) ou fazer parse de tokens separados por whitespace. É mais lento que operações diretas, mas muito flexível para serialização e desserialização.

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

Raw Strings & Multiline

Raw string literals R"(...)" tratam barras invertidas e aspas literalmente, ideais para padrões de regex, caminhos de arquivo do Windows e templates JSON/XML. Os delimitadores dentro dos parênteses são arbitrários, ex.: 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

Arrays de char estilo C exigem gerenciamento manual de tamanho e são propensos a erros (buffer overflows). Prefira std::string; use .c_str() ao interagir com APIs de C que esperam const char*. Note que c_str() só é válido enquanto a string está viva e não modificada.

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 & Matemática

Tipos Inteiros & de Ponto Flutuante

Use tipos de largura fixa de <cstdint> (int32_t, int64_t) quando o tamanho exato importar entre plataformas. O separador de dígitos ' (C++14) melhora a legibilidade de números grandes. double é o tipo de ponto flutuante padrão e é preferido a float para precisão.

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)

Limites Numéricos

<limits> fornece type traits para propriedades numéricas. Use-os em vez de macros INT_MAX hardcoded. epsilon() fornece a menor diferença distinguível por ponto flutuante, útil para comparar doubles com tolerância.

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();

Funções Matemáticas

<cmath> fornece funções matemáticas padrão. Overflow de inteiros é comportamento indefinido em C++; use int64_t ou verifique limites. Para código financeiro, lembre-se de que ponto flutuante é impreciso—considere centavos inteiros ou uma 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 Aleatórios

C++ moderno usa a biblioteca <random> em vez de rand(). mt19937 é um PRNG de alta qualidade. Distribuições (uniform_int, uniform_real, normal) mapeiam bits brutos para o intervalo desejado sem o viés de módulo que aflige 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 Inteiros & Casts

Overflow de inteiros com sinal é comportamento indefinido em C++ (o compilador pode otimizar assumindo que nunca acontece). Sempre faça cast para um tipo mais amplo antes de multiplicar, ou verifique limites. Overflow sem sinal faz wrap módulo 2^n e é bem 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

Fluxo de Controle

If / Else

C++17 introduz if com inicializador: if (auto it = m.find(k); it != m.end()) { ... }. Isso limita a variável ao bloco if/else, mantendo o escopo circundante limpo e evitando reuso acidental.

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

Sempre inclua break para prevenir fall-through não intencional. O atributo [[fallthrough]] do C++17 documenta fall-through intencional para silenciar avisos. Switch funciona com tipos integrais e enum, não strings ou 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";
}

Loops For

Range-based for itera containers de forma limpa. Use const auto& para evitar copiar elementos (importante para strings e objetos grandes). Para modificar elementos in-place, use auto& (referência não 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 & Do-While

while verifica a condição antes de executar; do-while executa o corpo pelo menos uma vez antes de verificar. do-while é útil para validação de entrada e loops de menu onde o corpo deve rodar antes que a condição possa ser avaliada.

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 & Loops Aninhados

break sai do loop delimitador mais próximo; continue pula para a próxima iteração. C++ não tem labeled break como Java; use uma variável flag, ou extraia o loop em uma função e use return para sair de loops aninhados.

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

Funções & Lambdas

Definir & Múltiplos Retornos

Structured bindings do C++17 (auto [a, b] = ...) descompactam tuples, pairs e structs de forma limpa. Antes do C++17, use std::tie ou parâmetros de saída. Retornar por valor é barato devido a move semantics (RVO) que elide a cópia.

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 & Inline

Argumentos padrão permitem que chamadores omitam parâmetros finais. inline é uma dica ao compilador para expandir a função inline; compiladores modernos decidem inlining por conta própria com base em flags de otimização, então inline é principalmente sobre ODR (regra de uma definição).

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 Funções

Sobrecarga permite que funções compartilhem um nome, mas difiram por tipos de parâmetro. O compilador escolhe a melhor correspondência via resolução de sobrecarga. Sobrecargas ambíguas causam erros de compilação; prefira templates quando o corpo for 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

Expressões Lambda

Lambdas criam objetos de função anônimos inline. O [] captura variáveis: [=] por valor, [&] por referência, [x] específica por valor, [&x] específica por referência. São essenciais para algoritmos STL e callbacks. Cuidado com referências pendentes ao capturar por referência.

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 << " ";
});

Ponteiros de Função & std::function

std::function (de <functional>) armazena qualquer callable: funções, lambdas, functors. É mais flexível que ponteiros de função crus, mas tem uma pequena sobrecarga de runtime devido a type erasure. Use para callbacks e armazenar callables em containers.

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

Containers STL

vector

vector é um array dinâmico e a escolha padrão de container. push_back é O(1) amortizado. .at() faz verificação de limites (lança std::out_of_range), operator[] não. Chame reserve() antecipadamente se você souber o tamanho para evitar realocações.

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 & unordered_map

map mantém as chaves ordenadas (operações O(log n)); unordered_map usa hashing (O(1) médio). Use map quando precisar de iteração ordenada ou consultas por intervalo; unordered_map para pura velocidade de lookup. Iterar unordered_map é não ordenado.

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 & unordered_set

set armazena elementos únicos ordenados (O(log n)). unordered_set é a versão baseada em hash (O(1) médio). Use-os para deduplicação e teste de pertencimento. lower_bound/upper_bound permitem consultas por intervalo em 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 & deque

array é um array de tamanho fixo alocado na stack com interface STL (mais seguro que arrays de C, sem decair para ponteiro). deque (fila de dupla extremidade) suporta push/pop O(1) em ambas as extremidades, diferentemente de vector que é O(n) na 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 & pair

tuple armazena valores heterogêneos de qualquer tipo. pair é uma tuple de 2 elementos. Structured bindings (C++17) os decompõem em variáveis nomeadas. Comum ao iterar maps cujos elementos são pairs de (chave, 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

Ponteiros & Memória

Ponteiros Crus

Ponteiros armazenam endereços de memória. & obtém o endereço, * faz a dereferência. Aritmética de ponteiros funciona em arrays. Ponteiros crus não rastreiam propriedade, levando a vazamentos e ponteiros pendentes—prefira smart pointers para recursos de propriedade.

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

Referências vs Ponteiros

Referências são mais seguras (nunca null, sempre válidas) e têm sintaxe mais limpa. Use referências para parâmetros de função e valores de retorno. Use ponteiros quando null for um estado significativo ou quando precisar reatribuir o que é apontado.

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 é propriedade exclusiva de um objeto heap. Não pode ser copiado, apenas movido. Exclui automaticamente quando sai do escopo (RAII). É o smart pointer padrão para a maioria dos casos de uso—sobrecarga zero sobre um ponteiro cru.

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 & weak_ptr

shared_ptr usa contagem de referências; o objeto é liberado quando o último shared_ptr é destruído. weak_ptr observa sem afetar a contagem, quebrando ciclos de referência. Evite ciclos de shared_ptr (eles vazam porque a contagem nunca chega a zero).

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 & new/delete

RAII vincula o tempo de vida do recurso ao escopo do objeto: construtores adquirem, destrutores liberam. Isso garante limpeza mesmo quando exceções se propagam. Prefira vectors e smart pointers a new/delete manual—eles implementam RAII para você.

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

Classes & OOP

Classe & Construtor

A lista de inicialização de membros (: name(...), age(...)) inicializa membros antes de o corpo executar, mais eficiente que atribuição no corpo. Marque getters como const para permitir chamadas em objetos const. std::move evita copiar o 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 Acesso & Encapsulamento

Membros private são acessíveis apenas dentro da classe; protected permite subclasses; public é aberto a todos. Encapsulamento esconde detalhes de implementação, expondo uma interface estável. Use private por padrão e exponha apenas o necessário.

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; }
};

Herança & Virtual

virtual permite polimorfismo em tempo de execução—chamar speak() através de um Animal* faz dispatch para a versão de Dog. Sempre declare um destrutor virtual em classes base para que excluir através de um ponteiro base chame o destrutor derivado. override captura erros de digitação.

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;

Classes Abstratas & Interfaces

Uma função virtual pura (= 0) torna a classe abstrata—você não pode instanciá-la. Classes com apenas virtuais puras agem como interfaces do Java. Subclasses concretas devem implementar todas as virtuais puras ou permanecem abstratas.

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

Sobrecarga de operadores permite que tipos de usuário funcionem com +, <<, ==, etc. Sobrecarregue apenas quando o significado for intuitivo (tipos matemáticos, iteradores). O operador << é comumente sobrecarregado via friend para saída de stream, permitindo 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 & Genéricos

Templates de Função

Templates de função geram versões específicas de tipo em tempo de compilação. O compilador deduz T a partir dos argumentos; você também pode especificá-lo explicitamente. Templates são abstrações de custo zero—sem sobrecarga de runtime, mas aumentam o tempo de compilação e o tamanho do binário.

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

Templates de Classe

Templates de classe parametrizam classes inteiras sobre tipos. Os containers padrão (vector, map) são todos templates. Código de template deve estar em headers (ou usar instanciação explícita) porque o compilador precisa da definição completa para gerar 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");

Especialização de Template

Especialização completa fornece uma implementação personalizada para um tipo específico. Especialização parcial (apenas para templates de classe) personaliza para uma categoria de tipos (ex.: todos os tipos de ponteiro). Útil para otimizar ou especializar comportamento.

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

Templates Variádicos

Templates variádicos aceitam qualquer número de argumentos via parameter packs (...). Eles usam recursão para processar cada argumento. Fold expressions do C++17 simplificam isso: (std::cout << ... << args). Usado intensivamente em 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) restringem parâmetros de template com requisitos legíveis, substituindo arcanos enable_if/SFINAE. Eles produzem mensagens de erro muito mais claras quando as restrições não são atendidas. Use concepts padrão 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 & find

Algoritmos STL operam em intervalos de iteradores [begin, end). sort é O(n log n). find é linear; para intervalos ordenados, use binary_search/lower_bound (O(log n)). Passe comparadores personalizados (lambdas ou std::greater) para ordenação 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 & for_each

transform mapeia cada elemento para um novo valor (como map em linguagens funcionais). for_each aplica uma função para efeitos colaterais. Ranges do C++20 permitem v | views::transform(...) para um estilo de pipeline mais limpo sem 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 & count

accumulate (de <numeric>) dobra um intervalo com uma operação. O terceiro argumento é o valor inicial e determina o tipo do resultado—use 0.0 para somas de double. count retorna quantos elementos são iguais a um valor; count_if usa um 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 & unique

remove na verdade não apaga—ele desloca elementos não correspondentes para frente e retorna um novo iterador final. Combine com .erase() para o idioma erase-remove. unique compacta duplicatas consecutivas de forma semelhante; ordene primeiro para deduplicar totalmente.

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 & clamp

min/max retornam o menor/maior de dois valores ou uma initializer list. minmax retorna ambos como um pair. clamp (C++17) restringe um valor a um intervalo, substituindo verificações de limites if/else manuais—útil para validação de entrada e 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

Tratamento de Erros

Exceções: try/catch

Lance exceções por valor, capture por const reference para evitar slicing. Capturar std::exception captura todas as exceções padrão via classe base. Exceções são para casos excepcionais, não fluxo de controle normal—elas têm sobrecarga quando lançadas.

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();
}

Hierarquia de Exceções Padrão

<stdexcept> fornece tipos de exceção padrão. logic_error é para erros do programador (detectáveis antes do runtime); runtime_error para condições de runtime imprevistas. Derive exceções personalizadas de std::runtime_error para que se integrem a blocos catch padrão.

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

Exceções Personalizadas

Derive exceções personalizadas de uma base padrão para que se integrem a catch(const std::exception&). Adicione campos de contexto (caminhos de arquivo, códigos de erro) que ajudam na depuração. Sempre passe a mensagem para o construtor 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 & Segurança RAII

noexcept promete que uma função não lançará, permitindo otimizações do compilador. Se lançar, std::terminate é chamado. RAII garante que destrutores executem durante stack unwinding, então recursos são liberados mesmo quando exceções se propagam pela call stack.

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
}

Asserções

assert() verifica condições em builds de depuração; é removido em release (quando NDEBUG é definido), então não o use para verificações de produção. Use-o para invariantes internos que indicam bugs. Para validação voltada ao usuário, lance exceções ou retorne códigos de erro.

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 & Streams

Lendo um Arquivo

ifstream abre um arquivo para leitura. Sempre verifique se a abertura teve sucesso (!file avalia true em caso de falha). getline lê linha por linha incluindo espaços. O destrutor do stream fecha o arquivo automaticamente (RAII), então não é necessário 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";
}

Escrevendo um Arquivo

ofstream escreve em um arquivo, truncando por padrão. Use std::ios::app para anexar, std::ios::binary para modo binário. O operador << funciona exatamente como std::cout. Descarregue com out.flush() ou use std::endl (que também descarrega).

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 constrói strings a partir de tipos mistos (como um buffer). istringstream faz parse de strings em valores tipados. São mais lentos que operações diretas de string, mas muito convenientes para serialização, construção de URLs e parse 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;

Arquivos Binários

Modo binário evita tradução de nova linha e é mais compacto que texto. write/read recebem char* e contagem de bytes—use reinterpret_cast para structs. Nota: arquivos binários não são portáveis entre arquiteturas (endianness, padding de struct diferem).

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));

Saída Formatada (C++20 fmt)

std::format (C++20) traz strings de formato estilo Python para o C++, substituindo manipuladores iomanip confusos. Para código mais antigo, <iomanip> fornece setprecision, setw, setfill. A biblioteca {fmt} é uma alternativa popular pré-C++20 com a mesma sintaxe.

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 - Propriedade Exclusiva

unique_ptr é o smart pointer padrão—use-o quando um proprietário é suficiente. Tem sobrecarga zero vs ponteiros crus. make_unique é preferido (exception-safe). Não pode copiar, apenas mover. Custom deleters permitem RAII para recursos de C como FILE* ou 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 - Propriedade Compartilhada

shared_ptr usa contagem de referências—múltiplos ponteiros podem possuir o mesmo objeto. O objeto é destruído quando o refcount chega a 0. make_shared é preferido (uma única alocação para objeto + bloco de controle). Mais pesado que unique_ptr devido ao refcount atômico e bloco de controle. Use quando a propriedade for genuinamente compartilhada.

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 - Quebrando Ciclos

weak_ptr é um observador não proprietário de um shared_ptr. Não aumenta o refcount. Use lock() para obter temporariamente um shared_ptr (retorna null se o objeto foi destruído). Essencial para quebrar ciclos de referência (ex.: listas duplamente encadeadas, relações pai-filho) que causariam vazamentos de memória.

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 com Arrays

Smart pointers podem gerenciar arrays. unique_ptr<T[]> fornece operator[] e delete[] correto. shared_ptr<T[]> é suportado desde C++17. No entanto, std::vector ou std::array são quase sempre melhores—mais seguros, mais ergonômicos e autodocumentáveis. Use smart array pointers apenas ao interagir com APIs legadas.

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

Quando um objeto precisa retornar um shared_ptr para si mesmo, enable_shared_from_this fornece shared_from_this() seguro. Chamar shared_ptr<T>(this) diretamente criaria um segundo bloco de controle, levando a double-free. O objeto já deve ser gerenciado por um shared_ptr, ou shared_from_this() lança 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

Move Semantics & Rvalue References

Lvalues, Rvalues e Referências

Lvalues têm identidade e persistem além de uma única expressão (objetos nomeados). Rvalues são valores temporários ou literais. T& vincula a lvalues, T&& vincula a rvalues. const T& é especial—vincula a ambos. Entender essa distinção é a base das move semantics.

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 e Move Constructors

std::move não move nada—faz cast para rvalue, permitindo que move constructor/assignment seja selecionado. Operações de move devem ser noexcept para que containers possam usá-las durante realocação (caso contrário, recorrem à cópia para segurança de exceção). Após um move, o objeto de origem está em um estado válido, mas não 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

Perfect forwarding passa argumentos para outra função preservando sua categoria de valor (lvalue vs rvalue). T&& em um contexto deduzido é uma 'forwarding reference' (não rvalue reference). std::forward<T> faz cast condicional: lvalue se T for T&, rvalue se T for T&&. Essencial para funções factory e 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)

Regra dos Cinco / Regra do Zero

Regra dos Cinco: se uma classe gerencia um recurso, você deve definir destrutor, copy ctor, copy assign, move ctor, move assign. Regra do Zero: prefira compor tipos RAII (vector, string, smart pointers) para que os membros especiais gerados pelo compilador estejam corretos. Isso elimina o gerenciamento manual de recursos propenso a bugs.

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 que o compilador construa o valor de retorno diretamente no armazenamento do chamador, evitando cópias/moves inteiramente. C++17 torna RVO obrigatório para prvalues. Nunca escreva return std::move(local)—isso inibe NRVO e força um move (mais lento). Apenas retorne o local por nome e deixe o compilador otimizar.

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

Concorrência (thread, mutex, async)

Básico de std::thread

std::thread lança uma nova thread do OS. Você DEVE chamar join() (esperar) ou detach() (deixar rodar independentemente) antes que o objeto thread seja destruído, senão std::terminate é chamado. Argumentos são passados por valor por padrão—use std::ref para referências, std::move para tipos move-only. Prefira join a menos que tenha um motivo claro 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 e Lock Guard

Sempre proteja dados compartilhados com um mutex. std::lock_guard é o lock RAII mais simples—adquire na construção, libera na destruição. std::scoped_lock (C++17) bloqueia com segurança múltiplos mutexes usando um algoritmo de avoidance de deadlock. std::unique_lock oferece mais flexibilidade (lock/unlock manual, locking diferido) para uso com 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 que threads esperem por uma condição. Sempre use um predicado com wait() para lidar com spurious wakeups. O mutex deve ser mantido por um unique_lock ao chamar wait(), que o libera enquanto espera e readquire antes de retornar. notify_one acorda um waiter, notify_all acorda todos. Esse padrão implementa filas thread-safe e pipelines producer-consumer.

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 e Futures

std::async é uma maneira de alto nível de executar tarefas assincronamente, retornando um future. std::launch::async força uma nova thread; std::launch::deferred executa preguiçosamente em get(). A política padrão pode escolher qualquer uma—seja explícito para comportamento previsível. Para mais controle, use pares std::promise/future. Sempre chame get() em um future antes da destruição, senão o destrutor pode bloquear.

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

Operações Atômicas

std::atomic fornece operações thread-safe sem lock para tipos primitivos. Mais pesado que um int comum, mas muito mais leve que mutex para contadores/flags simples. A ordenação de memória afeta visibilidade: relaxed (sem ordenação), acquire/release (par para sincronização), seq_cst (padrão, mais forte). Use atomics para contadores/flags; use mutex para seções críticas complexas.

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

Especialização de Template

Especialização de template fornece implementações personalizadas para tipos específicos. Especialização completa fixa todos os parâmetros de template. Especialização parcial (apenas para templates de classe) especializa alguns parâmetros enquanto mantém outros genéricos. Usado intensivamente em type traits, std::vector<bool> e otimização para tipos conhecidos.

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 e enable_if

SFINAE (Substitution Failure Is Not An Error) permite habilitar/desabilitar sobrecargas de template com base em propriedades de tipo. std::enable_if define condicionalmente um tipo. Quando a substituição falha, a sobrecarga é silenciosamente removida em vez de causar um erro. if constexpr do C++17 e concepts do C++20 frequentemente substituem SFINAE com sintaxe mais limpa.

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 avalia a condição em tempo de compilação e descarta o branch falso inteiramente (nem mesmo verificado por tipo). Isso substitui muitos padrões SFINAE por código muito mais limpo. Particularmente útil para recursão de template (o caso base termina a recursão) e para branching em type traits sem erros de instanciação.

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>();
}

Templates Variádicos e Fold Expressions

Templates variádicos aceitam qualquer número de argumentos via parameter packs (typename... Args). Fold expressions do C++17 aplicam um operador a todos os elementos do pack: unary fold (... op pack), binary fold (init op ... op pack). Antes do C++17, você precisava de recursão com um caso base. Templates variádicos impulsionam std::make_unique, std::tuple, funções semelhantes a 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)

Concepts (C++20) substituem SFINAE por restrições legíveis que revelam intenção. Eles produzem mensagens de erro muito melhores que SFINAE. Defina concepts com concept Name = constraint;. Use-os em parâmetros de template, cláusulas requires ou templates abreviados (auto com concept). A biblioteca padrão fornece muitos concepts úteis em <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

Expressões Lambda Aprofundado

Modos de Captura

Lambdas capturam variáveis do escopo delimitador. [=] captura tudo por valor, [&] por referência—conveniente, mas propenso a erros (referências pendentes, capturas não intencionais). Prefira capturas explícitas [x, &y] para clareza. Init captures [name = expr] (C++14) permitem renomear, mover e computar valores capturados. Capture [&] com cuidado—lambdas que sobrevivem ao escopo causam referências pendentes.

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)

Lambdas genéricos usam parâmetros auto (C++14) ou parâmetros de template explícitos (C++20). São essencialmente sobrecargas de operator() template geradas pelo compilador. Template lambdas do C++20 permitem acessar o parâmetro de tipo T diretamente. Lambdas recursivos precisam de std::function (ou deducing this do C++23) porque um lambda auto comum não pode se referir a si mesmo por nome antes de seu tipo ser conhecido.

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 e Stateful

Por padrão, variáveis capturadas por valor em um lambda são const—o operator() de chamada é const. A palavra-chave mutable remove essa const-ness, permitindo modificação de variáveis capturadas por valor (modificando a cópia, não a original). Isso permite lambdas stateful como contadores. Cada cópia de um lambda tem seu próprio estado capturado. Use std::function para armazenar lambdas de tipos diferentes em containers.

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 com Algoritmos STL

Lambdas brilham como predicados/comparadores para algoritmos STL. Eles substituem a antiga abordagem de functor (objeto de função) por código inline e legível. Usos comuns: comparadores para sort, predicados para find_if/remove_if/copy_if, transformações para transform. Ranges do C++20 tornam isso ainda mais limpo com o operador pipe para transformações composáveis.

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; });

Funções de Ordem Superior

Lambdas permitem padrões de programação funcional em C++. Funções podem retornar lambdas (closures) ou aceitá-los como parâmetros. std::function envolve qualquer callable para armazenamento com type erasure. Composição de funções constrói pipelines encadeando lambdas. Esse estilo é poderoso para callbacks, event handlers e pipelines de transformação de dados, embora abordagens baseadas em template evitem a sobrecarga 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 & Modules

Básico de Namespace

Namespaces previnem colisões de nomes em projetos grandes. Podem ser aninhados e ter alias para conveniência. Namespaces anônimos dão aos símbolos linkage interno (visíveis apenas na unidade de tradução atual), substituindo a palavra-chave static estilo C para esse propósito. Evite using namespace em headers—polui todos os arquivos que o incluem.

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
}

Declarações e Diretivas using

Declaração using (using std::cout) importa um único nome—segura e recomendada. Diretiva using (using namespace std) importa tudo—conveniente, mas arriscado, especialmente em headers (causa conflitos de nomes e ambiguidade). Mantenha diretivas using em escopo pequeno (arquivos de função/implementação) e nunca em headers. using enum do C++20 importa todos os enumeradores de um 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) encontra free functions com base nos namespaces de seus argumentos. É por isso que std::cout << x funciona sem std::operator<<—o compilador procura no namespace de x. ADL é essencial para sobrecargas de operadores e customization points como swap. O padrão 'using std::swap; swap(a,b);' permite que tipos de usuário forneçam swaps otimizados enquanto recorrem 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 (Versionamento)

Inline namespaces expõem seus membros como se estivessem no namespace delimitador. Isso permite versionamento de biblioteca: torne a versão mais recente inline para que os usuários a obtenham por padrão, enquanto versões antigas permanecem acessíveis via qualificação explícita. Também é usado para compatibilidade de ABI e alternância de recursos. Mudar qual namespace é inline desloca a versão padrão sem modificar o código do usuário.

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

Modules do C++20

Modules do C++20 substituem #include por um sistema mais rápido e robusto. export module declara um módulo; export marca declarações visíveis. Modules são compilados uma vez (não re-parseados por unidade de tradução), melhorando drasticamente os tempos de build. Eles evitam poluição de macros e problemas de ordem de headers. A adoção é gradual—o suporte de toolchain (CMake, build systems) ainda está amadurecendo em 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

Pré-processador & Macros

Include Guards e #pragma once

Include guards previnem que um header seja processado múltiplas vezes em uma unidade de tradução, evitando erros de redefinição. #ifndef/#define/#endif é padrão e portátil. #pragma once é mais simples e evita colisões de nomes de macros, mas é tecnicamente não padrão (suportado por todos os principais compiladores). Código moderno frequentemente usa #pragma once pela simplicidade.

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 semelhantes a Funções

Macros semelhantes a funções são substituição de texto. SEMPRE coloque cada argumento e toda a expressão entre parênteses para evitar bugs de precedência. O idioma do { ... } while (0) faz uma macro se comportar como uma única instrução. Macros não têm verificação de tipo, nem escopo, e podem ter efeitos colaterais (MAX(i++, j++) incrementa duas vezes). Prefira constexpr/inline/templates em 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 e Concatenation

# (stringification) converte um argumento de macro em um literal de string. ## (token pasting) concatena dois tokens em um. O truque de dois níveis STR/XSTR primeiro expande macros e depois as stringifica. Esses operadores são essenciais para geração de código, macros de logging e X-macros. C++ moderno frequentemente substitui esses por funções constexpr, mas eles permanecem úteis para geração de string/token em tempo de compilação.

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)

Compilação Condicional

Compilação condicional (#if, #ifdef, #ifndef, #elif, #else, #endif) inclui/exclui código em tempo de compilação. Usada para código específico de plataforma, builds de depuração e feature flags. defined(X) verifica se X está definido (valor irrelevante). #error aborta a compilação com uma mensagem. Prefira constexpr if quando possível—é type-safe e o código é sempre compilado (capturando erros em todas as configurações).

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 e __VA_OPT__

Macros predefinidas fornecem informações em tempo de compilação: __FILE__/__LINE__ para logging, __cplusplus para versão do padrão, __func__ para nomes de função (é um identificador, não uma macro). __VA_OPT__ (C++20) insere condicionalmente uma vírgula apenas quando __VA_ARGS__ é não vazio, corrigindo o problema da vírgula final em macros variádicas. __has_include (C++17) permite inclusão opcional de headers para portabilidade.

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

Design Patterns em C++

Singleton (Meyers' Singleton)

Singleton garante que uma classe tenha uma instância com acesso global. Meyers' Singleton (variável local estática) é a implementação C++ mais limpa—inicialização thread-safe é garantida desde C++11. Exclua operações de cópia para prevenir duplicação. Singletons são controversos (estado global, difíceis de testar); prefira injeção de dependência quando viável. Use quando genuinamente uma instância é necessária (logger, config, interface 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 é o idioma mais importante do C++: adquira recursos em construtores, libere em destrutores. Isso garante limpeza mesmo quando exceções são lançadas. Smart pointers, std::lock_guard, std::fstream e std::vector todos usam RAII. O destrutor executa durante stack unwinding, tornando código exception-safe natural. RAII elimina new/delete, lock/unlock, open/close manuais—abrace-o em todo lugar.

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

Pimpl Idiom (Pointer to Implementation)

Pimpl (Pointer to Implementation) esconde detalhes de implementação atrás de um ponteiro. Benefícios: compilação mais rápida (membros private não aparecem no header), verdadeira estabilidade de ABI (mudar Impl não quebra a ABI), dependências de include reduzidas. O destrutor deve ser definido no arquivo .cpp porque unique_ptr<Impl> precisa que Impl seja completo. Comum em design de bibliotecas para APIs estáveis.

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(); }

Observer Pattern

Observer permite que subjects notifiquem subscribers sobre mudanças sem acoplamento. std::function facilita aceitar lambdas, funções membro e functors. Para código de produção, considere um mecanismo de cancelamento de inscrição baseado em token (retorne um ID na inscrição, use-o para remover). Cuidado com referências pendentes se observers capturarem [&] e sobreviverem ao subject. Signals/slots do Qt e events do C# são implementações 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>) alcança polimorfismo estático—a classe base personaliza o comportamento via tipo derivado. Sem sobrecarga de função virtual. Usado para adicionar funcionalidade (Comparable, Iterable) a classes derivadas via mixins. Downcasting via static_cast é seguro porque o template garante o tipo derivado. CRTP impulsiona std::enable_shared_from_this, std::iterator e muitos designs baseados em policy.

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 Aprofundado

unique_ptr

unique_ptr é proprietário exclusivo de seu objeto. Não pode ser copiado, apenas movido. Exclui automaticamente quando sai do escopo. make_unique é a maneira preferida de criar. Sobrecarga zero vs ponteiros crus.

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últiplos proprietários via contagem de referências. use_count() mostra o número de proprietários. Thread-safe para o contador, mas não para o objeto. Use make_shared para eficiência (alocação única).

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 é uma referência não proprietária a shared_ptr. Previne referências circulares (vazamentos de memória). lock() tenta converter para shared_ptr. Use expired() para verificar se o objeto ainda existe. Não afeta a contagem de referência.

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

Custom deleters permitem que unique_ptr gerencie recursos não de memória como file handles, sockets e APIs de C. O tipo do deleter faz parte do tipo unique_ptr. Útil para RAII com 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 que um objeto obtenha com segurança um shared_ptr para si mesmo. Chamar shared_ptr(this) diretamente criaria uma segunda contagem de referência, causando double deletion. Herde de enable_shared_from_this e use 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

Move Semantics Aprofundado

Move Constructor

Move constructors roubam recursos em vez de copiar. noexcept é importante: containers STL só movem (não copiam) se o move for noexcept. Sempre deixe o objeto movido de origem em um 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 é um cast para rvalue reference. Não executa o move em si. O move constructor ou assignment operator faz o trabalho real. Após std::move, o objeto de origem está em um estado válido, mas não 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

Perfect forwarding preserva a categoria de valor dos argumentos. T&& em um template é uma forwarding reference, vinculando a ambos lvalues e rvalues. std::forward<T> faz cast de volta à categoria original. Essencial para funções factory e 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 & NRVO

RVO (Return Value Optimization) e NRVO (Named RVO) eliminam cópias construindo o objeto no local. Compiladores executam isso automaticamente. Usar std::move em um valor de retorno local previne NRVO e pode pessimizar. Confie no 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!

Regra dos Cinco

Regra dos Cinco: se você definir qualquer um entre destrutor, copy constructor/assignment ou move constructor/assignment, defina todos os cinco. Isso garante gerenciamento correto de recursos. A Regra do Zero é preferida: use tipos RAII (smart pointers, vectors) para evitar gerenciamento 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 Aprofundado

Templates Variádicos

Templates variádicos aceitam qualquer número de argumentos. Fold expressions (C++17) simplificam o unpacking. A abordagem recursiva funciona em C++11. sizeof...(args) fornece a contagem. Usado em tuple, make_shared e substitutos 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) remove sobrecargas inválidas da consideração. enable_if adiciona uma condição ao template. Se a condição for false, a especialização é ignorada. if constexpr do C++17 é frequentemente mais limpo.

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) avalia condições em tempo de compilação. Apenas o branch true é compilado, o outro é descartado. Muito mais limpo que SFINAE para compilação condicional. Funciona com dedução 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)

Concepts (C++20) restringem parâmetros de template com sintaxe legível. Eles fornecem melhores mensagens de erro que SFINAE. Use concepts existentes (integral, floating_point) ou defina personalizados. Concepts podem ser combinados com && e ||.

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; }

Especialização de Template

Especialização completa fornece uma implementação completa para um tipo específico. Especialização parcial personaliza para uma categoria (ex.: todos os ponteiros). O template primário deve ser declarado primeiro. Especializações devem corresponder à interface.

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 Aprofundado

sort & stable_sort

sort é O(n log n), não estável. stable_sort preserva a ordem relacional de elementos iguais. Use um comparador para ordenação personalizada. Ranges do C++20 permitem: 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 & accumulate

transform aplica uma função a cada elemento. accumulate dobra elementos com uma operação (padrão +). O valor inicial determina o tipo. Use std::reduce (C++17) para redução 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 & count

find retorna um iterador para a primeira correspondência ou end(). count retorna o número de correspondências. find_if usa um predicado. Todos são O(n). Para intervalos ordenados, use 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 & remove

copy_if copia elementos correspondentes. back_inserter anexa ao destino. remove na verdade não remove; ele desloca elementos não correspondentes para frente e retorna um novo final. erase remove o restante. C++20 adiciona std::erase para containers.

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 & clamp

minmax retorna um pair de (min, max) em uma passagem. clamp restringe um valor a um intervalo. min/max aceitam initializer lists ou dois valores. Todos aceitam comparadores personalizados. Structured bindings (C++17) simplificam o 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

Concorrência

std::thread

std::thread cria threads do OS. join() espera pela conclusão (bloqueante). detach() executa independentemente (pode sobreviver ao criador). Uma thread sem nenhum dos dois causa std::terminate. Passe argumentos por valor ou use std::ref para referências.

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 é RAII simples: bloqueia na construção, desbloqueia na destruição. unique_lock é mais flexível: pode desbloquear/bloquear novamente, usado com condition variables. Nunca desbloqueie manualmente com lock_guard. Use std::scoped_lock para múltiplos mutexes.

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 & futures

std::async executa uma função assincronamente, retornando um future. get() bloqueia e recupera o resultado. launch::async força uma nova thread. launch::deferred executa sincronamente em get(). O padrão pode ser qualquer um. Exceções são propagadas atravé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 threads. wait libera o lock e bloqueia até ser notificado. O predicado lida com spurious wakeups. notify_one acorda um waiter, notify_all acorda todos. Sempre modifique o estado compartilhado sob o 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()

Operações Atômicas

atomic fornece operações thread-safe sem lock. fetch_add/sub são read-modify-write. compare_exchange implementa CAS (compare-and-swap). memory_order_relaxed é mais rápido, mas mais fraco. seq_cst (padrão) é mais forte. Use atomics para contadores e 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

Recursos do C++ Moderno

Structured Bindings

Structured bindings (C++17) decompõem pairs, tuples e structs. auto& para referências, auto para cópias. Simplifica a iteração sobre maps. Funciona com qualquer tipo aggregate. Muito mais limpo 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 um valor que pode ou não existir. Melhor que ponteiros ou valores sentinela. has_value() ou operator bool verifica. value() lança se vazio, value_or() fornece um padrão. Use para funções que podem falhar em produzir um 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 é uma union type-safe. Armazena um de vários tipos. visit aplica um visitor (lambda sobrecarregado). index() retorna o índice do tipo atual. get_if<T> recupera com segurança. Substitui unions e herança para hierarquias de tipos fechadas.

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)

Ranges (C++20) fornecem views composáveis e preguiçosas. O operador | encadeia operações. Sem containers intermediários. Views são preguiçosas: a computação acontece na iteração. Muito mais legível que chamadas aninhadas de 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)

Coroutines (C++20) permitem padrões async e generator. co_yield suspende e retorna um valor. co_await espera por outra coroutine. co_return finaliza. O compilador transforma coroutines em state machines. Precisa de uma implementação 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

Armadilhas Comuns

Ponteiros Pendentes

Ponteiros pendentes apontam para memória liberada. Acessá-los é comportamento indefinido. Use smart pointers (unique_ptr, shared_ptr) para evitar gerenciamento manual de memória. Se usar ponteiros crus, defina como nullptr após delete. Use ferramentas como AddressSanitizer para detectar.

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;

Invalidação de Iterador

Vector push_back pode realocar, invalidando todos os iteradores. erase invalida iteradores no ponto e depois. Iteradores de list são estáveis, exceto para elementos apagados. Verifique as regras de invalidação de iterador para cada container. Em caso de dúvida, use í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);
}

Comportamento Indefinido

Comportamento indefinido (UB) permite que o compilador faça qualquer coisa. Variáveis não inicializadas, acesso fora dos limites, dereferência de null, overflow com sinal são todos UB. Compiladores otimizam assumindo que não há UB, levando a resultados surpreendentes. Use -fsanitize=undefined para detectar.

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

Slicing de objeto ocorre quando um objeto derivado é copiado para um tipo base. As partes derivadas são perdidas. Passe por referência ou ponteiro para evitar slicing. Chamadas virtuais em um objeto sliced chamam a versão base. Sempre passe tipos polimórficos por referência/ponteiro.

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 */ }

Inicialização Estática

A ordem de inicialização estática entre unidades de tradução é indefinida. Um arquivo pode usar um static de outro arquivo ainda não inicializado. Corrija com statics locais de função (Meyers Singleton): a inicialização é preguiçosa e thread-safe (C++11+). Acesso via chamada de função.

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.