библиотека — типы общего назначенияlibrary — general-purpose types
Контейнер, хранящий значение любого типа, известного только в рантайме — типобезопасная альтернатива void*. В отличие от variant, набор возможных типов не фиксирован на этапе компиляции.A container holding a value of any type, known only at runtime — a type-safe alternative to void*. Unlike variant, the set of possible types isn't fixed at compile time.
std::any — объект, способный хранить значение произвольного copy-constructible типа, определяемого динамически при присваивании. В отличие от std::variant<T1, T2, ...>, где набор допустимых типов перечислен в шаблонных параметрах, any не ограничен заранее заданным списком — в него можно положить значение любого типа в любой момент. Извлечение — через std::any_cast<T>(a), кидающий std::bad_any_cast при несовпадении реального типа, либо std::any_cast<T>(&a), возвращающий указатель или nullptr.std::any is an object capable of holding a value of any copy-constructible type, determined dynamically on assignment. Unlike std::variant<T1, T2, ...>, where the set of allowed types is listed as template parameters, any isn't limited to a predetermined list — you can put a value of any type into it at any moment. Extraction is via std::any_cast<T>(a), which throws std::bad_any_cast on a type mismatch, or std::any_cast<T>(&a), which returns a pointer or nullptr.
void* makeValue(int kind);
void* v = makeValue(1);
// static_cast<Widget*>(v) — компилятор ничего не проверяет,// static_cast<Widget*>(v) — the compiler checks nothing,
// перепутать реальный тип — UB, а не ошибка компиляции/исключение// getting the real type wrong is UB, not a compile error/exception
Widget* w = static_cast<Widget*>(v);
std::any makeValue(int kind);
std::any v = makeValue(1);
if (auto* w = std::any_cast<Widget>(&v)) {
use(*w); // безопасно — тип реально проверен в рантайме// safe — the type was actually checked at runtime
} // иначе w == nullptr, никакого UB// otherwise w == nullptr, no UB
Гетерогенное хранение значений произвольного типа встречается в реальных задачах чаще, чем хотелось бы, и до C++17 единственным типобезопасным способом было заранее знать список типов:Heterogeneous storage of arbitrary-type values comes up in real tasks more often than one would like, and before C++17 the only type-safe way was to know the list of types ahead of time:
Плагинные архитектуры, générик message passing, конфиги с произвольными по форме значениями — все они регулярно упирались в необходимость хранить значение, тип которого известен только вызывающему коду в конкретный момент, а не на этапе компиляции библиотеки. До C++17 это почти всегда решалось через void* с ручным приведением, полагаясь на дисциплину, а не на компилятор.Plugin architectures, generic message passing, configs with arbitrarily-shaped values — all of these regularly ran into the need to store a value whose type is known only to the calling code at a specific moment, not at the library's compile time. Before C++17 this was almost always solved via void* with manual casting, relying on discipline rather than the compiler.
Там, где легаси-код хранит void* и вручную приводит его к нужному типу без проверки, замена на std::any с any_cast добавляет проверку типа в рантайме почти бесплатно с точки зрения архитектуры — при несовпадении типа программа получает исключение или nullptr вместо тихого UB.Wherever legacy code stores a void* and manually casts it to the expected type without checking, replacing it with std::any plus any_cast adds a runtime type check almost for free architecturally — on a type mismatch, the program gets an exception or a nullptr instead of silent UB.
Как и optional, any унаследован из экспериментального Library Fundamentals TS v1 (2015) практически без изменения семантики — std::experimental::any в коде до C++17 ведёт себя почти идентично финальному std::any.Like optional, any was inherited from the experimental Library Fundamentals TS v1 (2015) with almost no change in semantics — std::experimental::any in pre-C++17 code behaves nearly identically to the final std::any.
any хранит указатель на type-erasure обёртку (обычно с виртуальными функциями clone/destroy/type-info внутри) плюс сам объект — либо инлайново в небольшом внутреннем буфере (small buffer optimization, для маленьких типов), либо в куче, выделенной через new (для крупных или не-noexcept-move типов). std::type_info, доступный через a.type(), позволяет узнать реальный хранимый тип в рантайме без попытки any_cast.any stores a pointer to a type-erasure wrapper (usually with virtual clone/destroy/type-info functions inside) plus the object itself — either inline in a small internal buffer (small buffer optimization, for small types), or on the heap allocated via new (for large or non-noexcept-movable types). std::type_info, available via a.type(), lets you learn the actually stored type at runtime without attempting an any_cast.
std::any a = 42;
std::cout << a.type().name(); // имя реального типа, обычно "i" для int (implementation-defined)// the real type's name, usually "i" for int (implementation-defined)
a = std::string("hello"); // смена типа — старое значение уничтожается, новое конструируется// type change — the old value is destroyed, the new one constructed
try {
int x = std::any_cast<int>(a); // кидает bad_any_cast — сейчас там string, не int// throws bad_any_cast — a string is stored now, not int
} catch (const std::bad_any_cast& e) {
// корректная обработка несовпадения типа// proper handling of the type mismatch
}
if (a.has_value() && a.type() == typeid(std::string)) {
auto& s = *std::any_cast<std::string>(&a); // без исключений, указатель уже проверен по типу// no exceptions, the pointer's type was already verified
}
Важное следствие: хранение крупного или не-noexcept-перемещаемого типа внутри any почти всегда означает выделение в куче при каждом присваивании нового значения — small buffer optimization спасает только маленькие тривиально-перемещаемые типы (реализация-зависимо, но обычно что-то около размера указателя-двух). Это делает any заметно дороже variant, у которого размер объекта фиксирован статически и куча не задействуется никогда.An important consequence: storing a large or non-noexcept-movable type inside any almost always means a heap allocation on every new value assignment — small buffer optimization only helps small, trivially movable types (implementation-defined, but usually around one-to-two pointer widths). This makes any noticeably more expensive than variant, whose object size is fixed statically and which never touches the heap.
Если набор возможных типов известен заранее — это задача для variant, не any. Если нужен полиморфизм по общему интерфейсу — это задача для обычной виртуальной иерархии или шаблонов. any стоит рассматривать как крайнюю меру для действительно гетерогенной рантайм-типизации (плагинные системы, générик message passing между независимо скомпилированными модулями), а не как удобный способ обойти проектирование типов.If the set of possible types is known ahead of time, that's a job for variant, not any. If polymorphism over a common interface is needed, that's a job for an ordinary virtual hierarchy or templates. any should be treated as a last resort for genuinely heterogeneous runtime typing (plugin systems, generic message passing between independently compiled modules), not as a convenient way to skip type design.
В отличие от variant, размер и стоимость которого фиксированы и известны статически, any почти всегда платит цену динамической аллокации для любого типа за пределами small-buffer-порога (обычно это исключает даже такие "небольшие" типы, как большинство пользовательских struct из нескольких полей). Использование any в горячем пути (hot loop) — частая скрытая причина просадки производительности.Unlike variant, whose size and cost are fixed and known statically, any almost always pays the price of a dynamic allocation for any type beyond the small-buffer threshold (which typically excludes even "smallish" types like most multi-field user structs). Using any in a hot loop is a common hidden cause of performance regressions.
Как и с optional::value() vs operator*, у any_cast два разных контракта: std::any_cast<T>(a) (по значению) кидает std::bad_any_cast при несовпадении, а std::any_cast<T>(&a) (по указателю) просто возвращает nullptr. Использовать первую форму в цикле проверки «а вдруг там T» без try/catch — почти гарантированный краш при первом же несовпадении.Just like optional::value() vs operator*, any_cast has two different contracts: std::any_cast<T>(a) (by value) throws std::bad_any_cast on a mismatch, while std::any_cast<T>(&a) (by pointer) simply returns nullptr. Using the first form in a "maybe it's T" checking loop without try/catch is an almost guaranteed crash on the first mismatch.