Dominando std::function e std::bind no C++ Moderno

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++.

Tags: cpp11 std::function std::bind Lambda STL

Publicado em 7-14 11:23