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.
#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.
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.
#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.
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 6Conversã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.
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);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.
#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"); // 7Comparaçã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.
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 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.
#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=30Raw 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.
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.
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 & 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.
#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.
#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.
#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 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.
#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.
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
}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.
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.
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).
// 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 & 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.
int n = 5;
while (n > 0) {
std::cout << n-- << " ";
}
int x;
do {
std::cin >> x;
} while (x < 0); // runs at least onceBreak, 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.
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; }
}
}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.
#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).
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 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.
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 versionExpressõ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.
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.
#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); // 12Containers 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.
#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.
#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.
#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 & 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.
#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 & 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).
#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);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.
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); // 2Referê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.
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 é 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.
#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 & 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).
#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ê.
// 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 neededClasses & 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.
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.
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.
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.
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.
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 & 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.
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"); // explicitTemplates 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.
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.
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); // specializedTemplates 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.
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) 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.
#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 & 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.
#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.
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 & 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.
#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 & 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.
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 & 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.
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); // 100Tratamento 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.
#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.
#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 messageExceçõ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.
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.
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.
#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 & 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.
#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).
#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.
#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).
#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.
#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 - 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.
#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.
#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 - 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.
#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.
#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
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.
#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 = 2Move 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.
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 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.
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
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.
#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.
// 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.
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
}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.
#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.
#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.
#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.
#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(); // 10Operaçõ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.
#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
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.
// 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.
#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.
#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.
#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>.
#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.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.
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)
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.
// 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.
#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.
#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.
#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 & 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.
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.
#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.
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.
// 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 codeModules 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.
// 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);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.
// 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 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.
// 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.
// # (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).
#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 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.
// 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>
#endifDesign 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).
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 é 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.
// 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 exceptionPimpl 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.
// 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.
#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>) 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.
// 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 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.
#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ú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).
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 é 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.
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.
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().
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
}
};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.
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.
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
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.
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.
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.
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 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.
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.
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.
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)
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 ||.
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.
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 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).
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.
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)).
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 & 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.
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.
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; });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.
#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 é 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.
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 & 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().
#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.
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.
#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_cstRecursos 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.
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.
#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.
#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.
#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)
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).
#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 coroutineArmadilhas 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.
// 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.
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.
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
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.
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.
// 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;
}Snippets 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
Referências rvalue e move constructors.
Expressões Lambda
Lambda e capturas.
Templates
Templates de função e de classe.
Contêineres STL
Operações comuns de contêiner.
Algoritmos STL
Funções de algoritmo comuns.
Iterador
Tipos e uso de iterador.
Tratamento de Exceções
try-catch e exceções personalizadas.
Multithreading
thread, mutex, condition_variable.
IO de Arquivo
Operações de leitura e escrita de arquivo.
Strings
Operações de std::string.
Expressões Regulares
Correspondência e substituição com std::regex.
Dedução de Tipo
auto, decltype, dedução de template.
constexpr
Constantes e computação em tempo de compilação.
Was this helpful?