💡 Resumen:Cómo funciona std::function por dentro (borrado de tipos, optimización de búfer pequeño) con ejemplos, y cuándo std::move_only_function de C++23 sustituye a std::function para invocables solo-movibles.
Última actualización:2026-10-24
std::function en C++ explicado en detalle¶
Hace poco, escribiendo código C++, usé is_function y aproveché para echar un vistazo a la implementación en el código fuente; descubrí algunas cosas. Antes aprendimos que eran tres puntos ..., pero ¿sabes qué son seis puntos ......?
Del mismo modo, si hay un fragmento de código como este:
¿Sabes qué significa?
¡Vamos a profundizar en los detalles juntos!
1. Los seis puntos¶
A los seis puntos solemos llamarlos two ellipsis operators; podemos dividirlos, y así obtenemos los tres casos siguientes
template <typename... Args> void foo1(Args......)
template <typename... Args> void foo2(Args... ...)
template <typename... Args> void foo3(Args..., ...)
Ahora está más claro; veamos el código de std::is_function.
2. Detalles de implementación de is_function¶
La implementación de std::is_function implica una plantilla principal y dos especializaciones parciales:
// 主模板,假设提供的类型不是函数类型
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 {};
- La plantilla principal asume que el tipo proporcionado no es un tipo función y devuelve
std::false_typecomo resultado. - La primera especialización parcial es para tipos función normales, donde
Retrepresenta el tipo de retorno yArgs...los tipos de los parámetros. Si el tipo proporcionado coincide con esa firma de función,std::true_typees el resultado. - La segunda especialización parcial es para tipos función variádicos, donde los puntos suspensivos indican que la función acepta un número variable de parámetros. Igualmente, si el tipo proporcionado coincide con esa firma de función,
std::true_typees el resultado.
3. Ejemplo de uso¶
Para terminar, un ejemplo 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;
}
Esto imprimirá:
¿Lo has aprendido? Fin de esta sección~
C++23 std::move_only_function vs std::function¶
El borrado de tipos que acabas de leer tiene un precio oculto: std::function exige que el invocable envuelto sea copiable, porque un envoltorio copiable es parte de su contrato. Captura un std::unique_ptr en una lambda e intenta almacenarla — el compilador se niega:
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, cabecera
Cómo elegir: conserva std::function cuando los invocables sean baratos de copiar y las APIs legadas lo exijan; pasa a std::move_only_function cuando la closure posee algo único. Recuerda además que std::function copia la closure al asignarla — con capturas pesadas es un coste real que move_only_function evita. En puntos sensibles al rendimiento, un parámetro de plantilla simple sigue ganando a ambos: sin borrado, sin indirección, totalmente inlineable.
Preguntas frecuentes¶
¿Qué diferencia hay entre std::function y std::move_only_function?¶
std::function exige invocables copiables y los copia al asignar; std::move_only_function (C++23) acepta invocables solo-movibles y solo los mueve. El comportamiento y la sintaxis de llamada son iguales.
¿Por qué std::function no puede contener una lambda que captura unique_ptr?¶
Porque std::function debe poder copiar el invocable almacenado, y el constructor de copia de la closure se elimina en cuanto captura un tipo solo-movible como unique_ptr. Usa std::move_only_function o un parámetro de plantilla.
¿Es std::move_only_function más rápida que std::function?¶
Puede ahorrarse la copia y algo de contabilidad, pero ambas pasan por borrado de tipos y una llamada indirecta. Si el punto de llamada conoce el tipo, toma el invocable como parámetro de plantilla: esa es la vía realmente rápida.
Temas relacionados¶
- Funciones virtuales puras y clases abstractas en C++ | C++那些事
- Aserciones assert en C++ | C++那些事
- Campos de bits en C++ | C++那些事
- Implementar el polimorfismo de C++ en lenguaje C | C++那些事
- const en C++ explicado: constantes y puntero const vs puntero a const | C++那些事
- La familia completa de const en C++ | C++那些事
- El operador de desreferencia * en C++ | C++那些事
- Inicialización y asignación en C++ | C++那些事
- Formas de devolver varios valores en C++ | C++那些事
- La palabra clave mutable en C++ | C++那些事