tgoop.com/cxx95/75
Create:
Last Update:
Last Update:
#story
Самое простое объяснение std::function за 15 минут
Этот пост был написан под влиянием крутого видео от Jason Turner "A Simplified std::function Implementation"
Часто люди не задумываются, как работает std::function
. Чаще всего знают, что эта штука - обертка над чем-то, что можно "вызвать" как функцию. Кто-то смутно помнит, что std::function
вроде как лезет в динамическую память. cppreference не сильно раскрывает внутренности реализации.
Можно сказать, что в C++ есть два типа объектов, на которых работает семантика вызова как функции. Можно условно назвать их Callable
. Это:
int foo(int a, int b) { return a + b; }
operator()
, часто их называют "функторы":struct foo {Все остальные
int operator()(int a, int b) { return a + b; }
}
Callable
являются производными от этих двух типов. В том числе лямбды - компилятор их переделывает в структуры с operator()
. Про лямбды есть хорошая книга - https://www.tgoop.com/cxx95/48.А
std::function<Signature>
должен уметь хранить все возможные Callable
с данной сигнатурой. template<typename Ret, typename... Param>Возникает проблема - у
class function<Ret(Param...)> {
// код реализации
};
std::function
должен быть фиксированный размер, но Callable
типа Поэтому, к сожалению,
std::function
хранит Callable
в куче.Также нужно использовать виртуальный класс, который для каждого отдельного типа как бы вычислит адрес вызываемого метода:
Виртуальный класс и указатель на кучу:
struct callable_interface {Реализация для каждого отдельного типа
virtual Ret call(Param...) = 0;
virtual ~callable_interface() = default;
};
std::unique_ptr<callable_interface> callable_ptr;
Callable
держит в себе сам объект Callable
и метод для вызова operator()
по правильному адресу:template<typename Callable>Конструктор
struct callable_impl : callable_interface {
callable_impl(Callable callable_) : callable{std::move(callable_)} {}
Ret call(Param... param) override { return std::invoke(callable, param...); };
Callable callable;
}
std::function
принимает Callable
и создает объект в куче:template<typename Callable>И наконец вызов
function(Callable callable)
: callable_ptr{std::make_unique<callable_impl<Callable>>(std::move(callable))}
{}
operator()
у самого std::function
перенаправляет вызов в содержимый Callable
:Ret operator()(Param... param) { return callable_ptr->call(param...); }Вот так выглядит один из способов type erasure в C++