Introdução
O C++11 trouxe consigo um conjunto poderoso de recursos para programação funci onal, incluindo a classe std::function e a função std::bind. Estes ferramentas permitem tratar funções como objetos de primeira classe, possibilitando uma flexibilidade extraordinária no design de software.
A Classe std::function
A classe std::function é um wrapper polimórfico que pode armazenar, copiar e invocar qualquer objeto chamável: ponteiros de função, funções membro, funtores e expressões lambda.
#include <functional>
#include <iostream>
#include <string>
using namespace std;
// Funções livres para demonstração
void imprimirMensagem(string mensagem) {
cout << mensagem << endl;
}
int somar(int x, int y) {
return x + y;
}
// Classe com método membro
class Calculadora {
public:
int multiplicar(int x, int y) {
return x * y;
}
};
int main() {
// Armazenando ponteiro de função
function<void(string)> func1 = imprimirMensagem;
func1("Olá, mundo!"); // Invoca a função através do operador()
// Armazenando função livre
function<int(int, int)> func2 = somar;
cout << func2(10, 20) << endl; // Saída: 30
// Armazenando lambda
function<int(int, int)> func3 = [](int a, int b) -> int {
return a * b;
};
cout << func3(5, 6) << endl; // Saída: 30
// Armazenando função membro (necessita instância do objeto)
function<int(Calculadora*, int, int)> func4 = &Calculadora::multiplicar;
Calculadora calc;
cout << func4(&calc, 7, 8) << endl; // Saída: 56
return 0;
}
Aplicação Prática: Sistema de Menu
Um uso muito comum de std::function é criar um sistema de menu flexível que elimina a necessidade de longos switches:
#include <functional>
#include <map>
#include <iostream>
using namespace std;
void exibirLivros() {
cout << "Exibindo todos os livros..." << endl;
}
void emprestarLivro() {
cout << "Emprestando livro..." << endl;
}
void consultarLivros() {
cout << "Consultando livros..." << endl;
}
int main() {
int opcao = 0;
map<int, function<void()>> menu;
menu[1] = exibirLivros;
menu[2] = emprestarLivro;
menu[3] = consultarLivros;
while (true) {
cout << "\n1 - Exibir livros" << endl;
cout << "2 - Emprestar" << endl;
cout << "3 - Consultar" << endl;
cout << "0 - Sair" << endl;
cout << "Escolha: ";
cin >> opcao;
if (opcao == 0) break;
auto it = menu.find(opcao);
if (it != menu.end()) {
it->second(); // Invoca a função armazenada
} else {
cout << "Opção inválida!" << endl;
}
}
return 0;
}
Especialização de Tepmlates
Entender especialização de templates é fundamental para compreender como std::function funciona internamente.
Especialização Total
#include <iostream>
using namespace std;
template<typename T>
class Container {
public:
void mostrar() { cout << "Versão genérica" << endl; }
};
// Especialização total para char*
template<>
class Container<char*> {
public:
void mostrar() { cout << "Versão especializada para char*" << endl; }
};
int main() {
Container<int> c1;
c1.mostrar(); // Versão genérica
Container<char*> c2;
c2.mostrar(); // Versão especializada para char*
return 0;
}
Especialização Parcial
#include <iostream>
using namespace std;
template<typename T>
class Memoria {
public:
Memoria() { cout << "Genérico" << endl; }
};
// Especialização parcial para ponteiros
template<typename T>
class Memoria<T*> {
public:
Memoria() { cout << "Ponteiro" << endl; }
};
// Especialização para funções
template<typename R, typename A1, typename A2>
class Memoria<R(A1, A2)> {
public:
Memoria() { cout << "Tipo função" << endl; }
};
// Especialização para ponteiros de função
template<typename R, typename A1, typename A2>
class Memoria<R(*)(A1, A2)> {
public:
Memoria() { cout << "Ponteiro de função" << endl; }
};
int calcular(int a, int b) { return a + b; }
int main() {
Memoria<int> m1; // Genérico
Memoria<int*> m2; // Ponteiro
Memoria<int(int, int)> m3; // Tipo função
Memoria<int(*)(int, int)> m4; // Ponteiro de função
return 0;
}
Dedução de Argumentos de Template
O compilador pode deduzir os tipos de template auotmaticamente com base nos argumentos passados:
#include <iostream>
#include <typeinfo>
using namespace std;
template<typename T>
void analizar(T valor) {
cout << "Tipo: " << typeid(T).name() << endl;
cout << "Valor: " << valor << endl;
}
int main() {
analizar(42); // T = int
analizar(3.14); // T = double
analisar("texto"); // T = const char*
return 0;
}
Implementação Simplificada de std::function
Para entender como std::function funciona internamente, vejamos uma implementação simplificada:
#include <iostream>
using namespace std;
// Implementação simplificada de function
template<typename Ret, typename... Args>
class Funtor {
public:
using FuncaoPonteiro = Ret(*)(Args...);
Funtor(FuncaoPonteiro pfn) : _fn(pfn) {}
Ret operator()(Args... args) {
return _fn(args...);
}
private:
FuncaoPonteiro _fn;
};
// Helper para dedução de tipo
template<typename Ret, typename... Args>
Funtor<Ret, Args...> criarFuntor(Ret(*fn)(Args...)) {
return Funtor<Ret, Args...>(fn);
}
void saudacao(string nome) {
cout << "Olá, " << nome << "!" << endl;
}
int main() {
auto func1 = criarFuntor(saudacao);
func1("Mundo");
Funtor<int(int, int)> func2 = [](int a, int b) -> int {
return a + b;
};
cout << func2(10, 20) << endl;
return 0;
}
std::bind: Criando Adaptadores de Função
A função std::bind permite criar novos objetos chamáveis com argumentos pré-fixados:
#include <functional>
#include <iostream>
#include <string>
using namespace std;
using namespace std::placeholders;
void mostrarMensagem(string titulo, string conteudo) {
cout << titulo << ": " << conteudo << endl;
}
int calcular(int a, int b, int c) {
return a + b + c;
}
class Processador {
public:
int processar(int valor) {
return valor * 2;
}
};
int main() {
// Bind com todos os argumentos
auto fn1 = bind(mostrarMensagem, "Aviso", "Sistema OK");
fn1(); // "Aviso: Sistema OK"
// Bind parcial - usando placeholders
auto fn2 = bind(mostrarMensagem, "info", _1);
fn2("Mensagem dinâmica"); // "info: Mensagem dinâmica"
// Múltiplos placeholders
auto fn3 = bind(calcular, _1, _2, 100);
cout << fn3(10, 20) << endl; // 130
// Bind com função membro
Processador proc;
auto fn4 = bind(&Processador::processar, &proc, _1);
cout << fn4(50) << endl; // 100
return 0;
}
Expressões Lambda
Expressões lambda são funções anónimas que podem ser definidas inline. São a forma mais expressiva de criar objetos chamáveis no C++11+.
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main() {
vector<int> numeros;
for (int i = 0; i < 10; i++) {
numeros.push_back(i);
}
// Lambda para filtrar números pares
vector<int> pares;
copy_if(numeros.begin(), numeros.end(), back_inserter(pares),
[](int n) { return n % 2 == 0; });
// Lambda para ordenação decrescente
sort(numeros.begin(), numeros.end(),
[](int a, int b] { return a > b; });
// Captura por valor [=] e por referência [&]
int multiplicador = 2;
for_each(numeros.begin(), numeros.end(),
[multiplicador](int& n) { n *= multiplicador; });
// Captura mutável (permite modificar cópias)
int x = 10, y = 20;
auto swapVars = [x, y]() mutable {
int temp = x;
x = y;
y = temp;
};
swapVars();
// Armazenando lambda em function
function<int(int, int)> operacao = [](int a, int b) -> int {
return a + b;
};
cout << operacao(5, 3) << endl;
return 0;
}
Tabela de Captura de Lambdas
| Sintaxe | Descrição |
|---|---|
[] |
Não captura variáveis externas |
[=] |
Captura todas por valor (cópia) |
[&] |
Captura todas por referência |
[this] |
Captura o ponteiro this |
[a, &b] |
Captura 'a' por valor e 'b' por referência |
Aplicações Avançadas
Priority Queue com Lambda
#include <functional>
#include <queue>
#include <iostream>
using namespace std;
struct Item {
int prioridade;
string nome;
};
int main() {
// Heap mínimo (menor prioridade no topo)
priority_queue<Item, vector<Item>,
function<bool(const Item&, const Item&)>>
filaMin([](const Item& a, const Item& b) {
return a.prioridade > b.prioridade;
});
// Heap máximo (maior prioridade no topo)
priority_queue<Item, vector<Item>,
function<bool(const Item&, const Item&)>>
filaMax([](const Item& a, const Item& b) {
return a.prioridade < b.prioridade;
});
filaMin.push({3, "baixa"});
filaMin.push({1, "urgente"});
filaMin.push({2, "média"});
while (!filaMin.empty()) {
cout << filaMin.top().nome << endl;
filaMin.pop();
}
return 0;
}
Smart Pointers com Deletors Customizados
#include <memory>
#include <cstdio>
#include <functional>
using namespace std;
int main() {
// unique_ptr com deleter personalizado via lambda
unique_ptr<FILE, function<void(FILE*)>>
arquivo(fopen("dados.txt", "w"),
[](FILE* fp) {
if (fp) fclose(fp);
});
if (arquivo) {
fprintf(arquivo.get(), "Dados gravados com sucesso\n");
}
return 0;
}
Conclusão
Os recursos de programação funcional introduzidos no C++11 representam uma mudança paradigmática na forma como escrevemos código C++. std::function, std::bind e expressões lambda formam um toolkit poderoso que permite:
- Tratar funções como objetos de primeira classe
- Criar adaptadores de funções flexíveis
- Implementar padrões de design como Strategy e Command de forma elegante
- Escrever código mais expressivo e conciso
A prática constante com esses recursos é essencial para dominar a programação moderna em C++.