Перейти к содержанию

💡 Кратко:Как устроен std::function внутри (стирание типа, малая буферная оптимизация) с примерами, и когда std::move_only_function из C++23 заменяет std::function для перемещаемых-но-не-копируемых вызываемых объектов.

Последнее обновление:2026-10-24

C++ подробный разбор std::function

Недавно, пишу код на C++, я воспользовался is_function и заодно взглянул на реализацию в исходниках — и обнаружил некоторые вещи: раньше мы изучали три точки ..., а что такое шесть точек ...... вы знаете?

Аналогично, если есть такой код:

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

знаете ли вы, что он означает?

Давайте вместе разберёмся в деталях!

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;
}

Это выведет:

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

Ну как, разобрались? На этом раздел окончен~

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?

Она экономит на копировании и части учёта, но обе проходят стирание типа и косвенный вызов. Если место вызова знает тип, передавайте вызываемый объект шаблонным параметром — это по-настоящему быстрый путь.

Похожие темы

Комментарии