Ir para o conteúdo

💡 Resumo:Como o std::function funciona por dentro (apagamento de tipos, otimização de buffer pequeno) com exemplos, e quando o std::move_only_function do C++23 substitui o std::function para invocáveis move-only.

Última atualização:2026-10-24

std::function em C++ explicado em detalhes

Recentemente, ao escrever código C++, usei is_function e, de passagem, olhei a implementação no código-fonte e descobri alguns problemas. O que aprendemos antes foram três pontos ..., mas você sabe o que são seis pontos ......?

Da mesma forma, se houver um código assim:

template<typename... Args>
struct foo(Args......);

Você sabe o que ele significa?

Vamos mergulhar nos detalhes juntos!

1.Seis pontos

Os seis pontos costumam ser chamados de two ellipsis operators; podemos dividi-los em três casos:

template <typename... Args> void foo1(Args......)
template <typename... Args> void foo2(Args... ...)
template <typename... Args> void foo3(Args..., ...)

Agora fica claro. Vejamos o código de std::is_function.

2.Detalhes da implementação de is_function

A implementação de std::is_function envolve o template primário e duas especializações parciais:

// 主模板,假设提供的类型不是函数类型
template<typename T>
struct is_function : std::false_type {};

// 偏特化,用于正常的函数类型
template<typename Ret, typename... Args>
struct is_function<Ret(Args...)> : std::true_type {};

// 偏特化,用于变参函数类型
template<typename Ret, typename... Args>
struct is_function<Ret(Args......)> : std::true_type {};
  • O template primário assume que o tipo fornecido não é um tipo de função e produz std::false_type como resultado.
  • A primeira especialização parcial é usada para tipos de função normais, em que Ret representa o tipo de retorno e Args... os tipos dos parâmetros. Se o tipo fornecido corresponder a essa assinatura de função, std::true_type é o resultado.
  • A segunda especialização parcial é usada para tipos de função variádicos, em que as reticências indicam que a função aceita um número variável de parâmetros. Da mesma forma, se o tipo fornecido corresponder a essa assinatura de função, std::true_type é o resultado.

3.Exemplo de uso

Por fim, um exemplo de uso de std::is_function:

#include <iostream>
#include <type_traits>

int func(int x, int y) { return x + y; }
int vararg_func(int x, ...) { return x; }

int main() {
    std::cout << std::boolalpha;
    std::cout << "Is func a function? " << std::is_function<decltype(func)>::value << std::endl;
    std::cout << "Is vararg_func a function? " << std::is_function<decltype(vararg_func)>::value << std::endl;
    return 0;
}

Isso produzirá:

Is func a function? true
Is vararg_func a function? true

Ficou claro? Fim desta seção~

C++23 std::move_only_function vs std::function

O apagamento de tipos que você acabou de ler tem um preço oculto: o std::function exige que o invocável embrulhado seja copiável, porque um wrapper copiável faz parte do contrato dele. Capture um std::unique_ptr numa lambda e tente armazenar — o compilador recusa:

auto make_sink() {
    auto ptr = std::make_unique<std::vector<int>>(42);
    return [ptr = std::move(ptr)] { return ptr->size(); };  // move-only closure
}

// std::function<std::size_t()> f = make_sink();   // ERROR: closure not copyable
std::move_only_function<std::size_t()> g = make_sink();  // OK since C++23

std::move_only_function (C++23, cabeçalho ) derruba o requisito de cópia. Aceita closures move-only, e por isso é o tipo natural para pipelines, tarefas assíncronas e callbacks que possuem recursos exclusivos como file handles ou conexões.

Como escolher: mantenha std::function quando os invocáveis são baratos de copiar e APIs legadas exigem; troque para std::move_only_function quando a closure possui algo único. Lembre também que std::function copia a closure na atribuição — com capturas pesadas é um custo real que move_only_function evita. Em pontos sensíveis a desempenho, um parâmetro de template simples ainda vence os dois: sem apagamento, sem indireção, totalmente inlineável.

Perguntas frequentes

Qual a diferença entre std::function e std::move_only_function?

std::function exige invocáveis copiáveis e os copia na atribuição; std::move_only_function (C++23) aceita invocáveis move-only e apenas os move. O comportamento e a sintaxe de chamada são iguais.

Por que std::function não consegue guardar uma lambda que captura unique_ptr?

Porque std::function precisa poder copiar o invocável armazenado, e o construtor de cópia da closure é deletado assim que ela captura um tipo move-only como unique_ptr. Use std::move_only_function ou um parâmetro de template.

std::move_only_function é mais rápido que std::function?

Ele poupa a cópia e parte da contabilidade, mas ambos passam por apagamento de tipos e chamada indireta. Se o ponto de chamada conhece o tipo, receba o invocável como parâmetro de template — esse é o caminho realmente rápido.

Tópicos relacionados

Comentários