Стирание типов: std::function, std::any и создание собственной обёртки
Скрытие конкретного типа за единым интерфейсом с семантикой значений
std::function стирает любой вызываемый объект с заданной сигнатурой; std::any стирает любое копируемое значение. Оба дают полиморфизм времени выполнения без общего базового класса и с обычной семантикой значений - вы копируете, перемещаете и храните их как int. Внутри это дисциплинированный союз шаблонов и виртуальной диспетчеризации, и, увидев этот приём однажды, вы сможете построить свою обёртку.
- объяснить, что значит "стереть" тип и почему важна семантика значений,
- использовать
std::functionдля хранения разнородных вызываемых объектов за одной сигнатурой, - использовать
std::anyиstd::any_castдля хранения и извлечения значений неизвестного типа, - реализовать идиому Concept/Model, чтобы построить свою обёртку со стиранием типов,
- рассуждать о стоимости: выделение в куче, косвенность и оптимизация малого буфера.
1. Задача: полиморфизм без базового класса
Вы уже знаете два способа писать код, работающий со многими типами. Шаблоны дают полиморфизм времени компиляции: template <class T> порождает новую функцию под каждый тип, без затрат во время выполнения, но тип проникает в каждую сигнатуру - std::vector<T> может хранить только один T. Виртуальные функции дают полиморфизм времени выполнения, но они навязчивы: каждый хранимый тип должен наследоваться от вашего базового класса и работать через указатель, поэтому вы теряете семантику значений и не можете хранить типы, которые вам не подконтрольны (например int или чужую структуру).
Стирание типов - третий путь. Оно позволяет написать один нешаблонный класс - скажем Drawable - который может хранить любой тип, предоставляющий операцию draw(), без наследования, и ведёт себя как обычное значение, которое можно скопировать в std::vector<Drawable>. Конкретный тип "стёрт": после конструирования обёртка помнит лишь как рисовать, а не что она хранит.