Класс function
представляет собой полиморфную оболочку для функциональных объектов, а именно
для тех, чей тип
удовлетворяет Callable
и CopyConstructible.
Это может быть указатель на функцию, лямбда, класс с перегруженным operator()
и т.д. Хранимый функциональный объект в терминах стандарта принято
называть target
.
Храним полем unique_ptr<concept>
, где concept
— класс с виртуальными
функциями (например, operator()
). При конструировании из функционального
объекта типа F
создаём в динамической памяти model<F>
, где хранится сам F
,
а также определены те самые виртуальные функции.
Функциональные объекты часто не имеют состояния вообще, или имеют, но небольшое.
Поэтому возникает естественное желание легковесные объекты хранить
непосредственно внутри function
, вместо указателя на динамическую память.
К сожалению, нам больше не подойдёт unique_ptr
, ведь в случае small object
не будет указателя на динамическую память. Во-первых, не очень понятно, какой
у такого unique_ptr
'а должен быть Deleter
. Во-вторых, а как тогда правильно
мувать small object'ы?
Разделим хранилище и логику на два поля:
- выровненный массив байт, где будет лежать либо сам функциональный объект (в случае small object), либо указатель на него;
- указатель на дескриптор — набор вспомогательных функций для оперирования хранилищем (может быть выражен классом с виртуальными функциями).
В зависимости от типа функционального объекта, будем хранить указатели на дескрипторы с разными динамическими типами.
Не имеет смысл когда-либо иметь два инстанса дескриптора с одинаковым динамическим типом — они эквивалентны. Мы можем этим воспользоваться, чтобы избежать каких-либо динамических аллокаций для дескрипторов — будем просто хранить по одному инстансу каждого дескриптора статически, и ссылаться на них.
Дефолтный конструктор создаёт пустой (empty) function
, который не хранит в
себе никакой функциональный объект, а при вызове operator()
кидает bad_function_call
. Для него удобно сделать отдельный дескриптор.
В отличие от стандартной библиотеки, ваш function
должен гарантировать
небросающие мувающие операции (конструктор и оператор присваивания). К
сожалению, в общем случае это невозможно при SOO, поэтому используйте эту
оптимизацию только для тех T
, у которых мув и сам не бросает.
Метод F* target<F>()
должен возвращать указатель на текущий функциональный
объект, если его динамический тип совпадает с F
, и nullptr
иначе.