💡 Кратко:Как устроен std::function внутри (стирание типа, малая буферная оптимизация) с примерами, и когда std::move_only_function из C++23 заменяет std::function для перемещаемых-но-не-копируемых вызываемых объектов.
Последнее обновление:2026-10-24
C++ подробный разбор std::function¶
Недавно, пишу код на C++, я воспользовался is_function и заодно взглянул на реализацию в исходниках — и обнаружил некоторые вещи: раньше мы изучали три точки ..., а что такое шесть точек ...... вы знаете?
Аналогично, если есть такой код:
знаете ли вы, что он означает?
Давайте вместе разберёмся в деталях!
1.Шесть точек¶
Шесть точек обычно называют two ellipsis operators; их можно разбить на части и получить три следующих случая
template <typename... Args> void foo1(Args......)
template <typename... Args> void foo2(Args... ...)
template <typename... Args> void foo3(Args..., ...)
Теперь всё наглядно; взглянем на код std::is_function.
2.Детали реализации is_function¶
Реализация std::is_function включает главный шаблон и две частичные специализации:
// 主模板,假设提供的类型不是函数类型
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 {};
- Главный шаблон предполагает, что предоставленный тип не является типом функции, и возвращает
std::false_type. - Первая частичная специализация предназначена для обычных типов функций, где
Retобозначает возвращаемый тип, аArgs...— типы параметров. Если предоставленный тип соответствует такой сигнатуре функции, результатом будетstd::true_type. - Вторая частичная специализация предназначена для типов функций с переменным числом аргументов, где многоточие означает, что функция принимает переменное количество параметров. Аналогично, если предоставленный тип соответствует такой сигнатуре функции, результатом будет
std::true_type.
3.Пример использования¶
Напоследок приведу пример использования 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;
}
Это выведет:
Ну как, разобрались? На этом раздел окончен~
C++23 std::move_only_function против std::function¶
У прочитанного вами стирания типа есть скрытая цена: std::function требует, чтобы завёрнутый вызываемый объект был копируем, — копируемая обёртка часть его контракта. Захватите std::unique_ptr в лямбде и попробуйте сохранить — компилятор откажет:
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, заголовок
Как выбирать: оставайтесь на std::function, когда вызываемые объекты дёшево копируются и legacy-API этого требует; переходите на std::move_only_function, когда замыкание чем-то единолично владеет. Помните также, что std::function копирует замыкание при присваивании — при тяжёлых захватах это реальная цена, которой move_only_function избегает. В чувствительных к производительности местах обычный шаблонный параметр по-прежнему бьёт обоих: без стирания, без косвенности, полностью встраивается.
Часто задаваемые вопросы¶
Чем std::move_only_function отличается от std::function?¶
std::function требует копируемых вызываемых объектов и копирует их при присваивании; std::move_only_function (C++23) принимает только-перемещаемые и только перемещает. Поведение и синтаксис вызова совпадают.
Почему std::function не может хранить лямбду с захватом unique_ptr?¶
Потому что std::function должна уметь копировать хранимый вызываемый объект, а копирующий конструктор замыкания удаляется, как только оно захватывает только-перемещаемый тип вроде unique_ptr. Используйте std::move_only_function или шаблонный параметр.
std::move_only_function быстрее std::function?¶
Она экономит на копировании и части учёта, но обе проходят стирание типа и косвенный вызов. Если место вызова знает тип, передавайте вызываемый объект шаблонным параметром — это по-настоящему быстрый путь.
Похожие темы¶
- C++ чисто виртуальные функции и абстрактные классы | C++那些事
- C++ утверждения assert | C++那些事
- C++ битовые поля | C++那些事
- Реализация полиморфизма C++ на языке C | C++那些事
- C++ const подробно: константы, указатель на const и const-указатель | C++那些事
- C++ всё семейство const | C++那些事
- C++ оператор разыменования * | C++那些事
- C++ инициализация и присваивание | C++那些事
- C++ возврат нескольких значений | C++那些事
- C++ ключевое слово mutable | C++那些事