C++: Adaptadores STL
Na lição 38 aprendemos sobre objetos de função.
Agora, vamos aprender sobre adaptadores de função — transformando funções existentes na forma que você precisa.
Como blocos de LEGO, usando peças pequenas para construir algo maior.
1. Visão Geral de Adaptadores
(1) 1.1 O que são Adaptadores?
Adaptadores são templates que modificam o comportamento de objetos de função.
Adaptadores comuns:
std::bind(vincular argumentos)std::ref(passar por referência)std::negate(negação)std::mem_fn(ponteiro para função membro)
2. std::bind — Vinculação de Argumentos
(1) 2.1 Uso Básico
std::bind é usado para vincular argumentos de função, criando novos objetos de função.
Exemplo: Vinculando argumentos (Dificuldade ⭐⭐)
▶ Exemplo 2: Exemplo de Código (Dificuldade ⭐)
#include <iostream>
#include <functional>
int add(int a, int b) {
return a + b;
}
int main() {
// Bind the first argument of add to 10
auto add10 = std::bind(add, 10, std::placeholders::_1);
std::cout << "add10(5) = " << add10(5) << std::endl; // Output: 15
std::cout << "add10(20) = " << add10(20) << std::endl; // Output: 30
return 0;
}
Saída:
add10(5) = 15
add10(20) = 30
💡 Dica:
std::placeholders::_1significa "deixe o primeiro argumento para ser preenchido depois"
(2) 2.2 Reordenando Argumentos
Exemplo: Trocando a ordem dos argumentos (Dificuldade ⭐⭐)
#include <iostream>
#include <functional>
int subtract(int a, int b) {
return a - b;
}
int main() {
// Swap argument order
auto reverse_subtract = std::bind(subtract,
std::placeholders::_2,
std::placeholders::_1);
std::cout << "subtract(10, 3) = " << subtract(10, 3) << std::endl; // 7
std::cout << "reverse(10, 3) = " << reverse_subtract(10, 3) << std::endl; // -7
return 0;
}
3. std::ref — Empacotamento de Referência
(1) 3.1 O Problema: Passagem por Valor
Por padrão, algoritmos STL passam objetos de função por valor, o que significa que o estado não pode ser compartilhado.
Exemplo: Resolvendo com std::ref (Dificuldade ⭐⭐)
#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>
struct Counter {
int count = 0;
void operator()(int) { count++; }
};
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
Counter counter;
// ❌ Wrong! Pass by value, a copy of counter is called
std::for_each(v.begin(), v.end(), counter);
std::cout << "Count: " << counter.count << std::endl; // Output: 0
// ✅ Correct! Use std::ref to pass by reference
std::for_each(v.begin(), v.end(), std::ref(counter));
std::cout << "Count: " << counter.count << std::endl; // Output: 5
return 0;
}
4. std::not_fn — Negação
(1) 4.1 Uso Básico
std::not_fn é usado para negar o valor de retorno de um objeto de função.
Exemplo: Negando um predicado (Dificuldade ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// Find the first even number
auto it1 = std::find_if(v.begin(), v.end(),
(int x) { return x % 2 == 0; });
std::cout << "First even: " << *it1 << std::endl; // 2
// Find the first odd number (negation)
auto it2 = std::find_if(v.begin(), v.end(),
std::not_fn((int x) { return x % 2 == 0; }));
std::cout << "First odd: " << *it2 << std::endl; // 1
return 0;
}
5. std::mem_fn — Ponteiro para Função Membro
(1) 5.1 O Problema: Ponteiros para Funções Membro São Difíceis de Usar
A sintaxe de ponteiro para função membro é complexa; std::mem_fn a simplifica.
Exemplo: Chamando funções membro (Dificuldade ⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
struct Student {
std::string name;
void display() const {
std::cout << "Student: " << name << std::endl;
}
};
int main() {
std::vectorStudent students = {{"Zhang San"}, {"Li Si"}};
// Use std::mem_fn to call member function
std::for_each(students.begin(), students.end(),
std::mem_fn(&Student::display));
return 0;
}
6. Exemplo Abrangente
▶ Exemplo 1: Processador de Notas Flexível (Dificuldade ⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> scores = {85, 92, 78, 90, 88};
int threshold = 90;
// Count how many scores are not lower than threshold
int count = std::count_if(scores.begin(), scores.end(),
std::bind(std::greater_equalint(),
std::placeholders::_1,
threshold));
std::cout << "Number of scores >= " << threshold << ": " << count << std::endl;
return 0;
}
Saída:
Number of scores >= :
❓ Perguntas Frequentes
P: std::bind ainda é relevante no C++11? R: Sim, mas Lambda é mais recomendada. Lambda é mais concisa e tem melhor desempenho.
// Using std::bind
auto f1 = std::bind(add, 10, std::placeholders::_1);
// Using Lambda (recommended)
auto f2 = (int x) { return add(10, x); };
P: Quando devo usar adaptadores? R: Quando uma função existente quase atende às suas necessidades, mas os argumentos não correspondem.
P: stack e queue podem usar outros contêineres como implementação subjacente? R: Sim! Adaptadores especificam o contêiner subjacente através de parâmetros de template:
stack<int, vector<int>>usa vector;queue<int, list<int>>usa list. Por padrão, stack usa deque, e queue também usa deque.
▶ Exemplo 3: Fila (Dificuldade ⭐)
#include <iostream>
#include <queue>
int main() {
std::queue<int> q;
q.push(10);
q.push(20);
q.push(30);
while (!q.empty()) {
std::cout << "Front: " << q.front() << std::endl;
q.pop();
}
return 0;
}
Saída:
Front:
push() para enfileirar, front() para acessar o elemento da frente e pop() para desenfileirar.
📖 Resumo
std::stack: adaptador de pilha, Último-a-Entrar-Primeiro-a-Sair (LIFO)std::queue: adaptador de fila, Primeiro-a-Entrar-Primeiro-a-Sair (FIFO)std::priority_queue: fila de prioridade, desenfileira por prioridadestd::bind: vinculação de argumentos de função
📝 Exercícios
-
Básico (Dificuldade ⭐): Crie uma
stackint, empilhe 1, 2, 3 em ordem, depois faça um loop e desempilhe todos os elementos. Observe a ordem de saída. -
Intermediário (Dificuldade ⭐⭐): Use
queuepara implementar uma "fila de trabalhos de impressão" — simule múltiplos trabalhos de impressão sendo processados em ordem, exibindo o comprimento restante da fila após cada trabalho ser processado. -
Desafio (Dificuldade ⭐⭐⭐): Use
priority_queuepara implementar um "escalador de tarefas" — cada tarefa tem uma prioridade (1-10), a fila processa tarefas da prioridade mais alta para a mais baixa, com empates desempatados pela ordem de inserção.
- Adaptadores: stack/queue/priority_queue encapsulam contêineres subjacentes
- stack LIFO: push/pop/top
- queue FIFO: push/pop/front/back
- priority_queue fila de prioridade: max-heap
- Adaptadores especificam contêiner subjacente através de parâmetros de template
Próxima lição: Tratamento de Exceções (#40)