W programowaniu często potrzebujemy elastycznych rozwiązań do przechowywania różnych danych, które mogą mieć różne typy — od liczb całkowitych, przez ciągi tekstowe, aż po złożone struktury. Klasa std::any
, dostępna od standardu C++17, jest bardzo użytecznym narzędziem pozwalającym przechować obiekt dowolnego typu w jednym zmiennym obiekcie.
std::any
to typ opakowujący, który może przechowywać obiekt dowolnego typu, nieznanego w czasie kompilacji. Innymi słowy, to pudełko, które pozwala „zapakować” dowolny obiekt i przenieść go, a następnie bezpiecznie odzyskać jego oryginalny typ w czasie wykonania programu.
Podstawową cechą std::any
jest przechowywanie informacji o typie przechowywanego obiektu (przy pomocy RTTI — run-time type information), co pozwala na dynamiczne i bezpieczne rzutowanie do właściwego typu przy odczycie.
Klasa ta różni się od std::variant
, która wymaga z góry deklaracji wszystkich możliwych typów, jakie może przechowywać. std::any
jest bardziej uniwersalne i pozwala na przechowywanie dosłownie dowolnego typu, także tego, który pojawi się dopiero podczas działania programu.
Mechanizm działania std::any
opiera się na ukrytym wewnętrznym wskaźniku do obiektu przechowywanego w pamięci, który może być dynamicznie alokowany (np. gdy typ jest duży lub nie da się go przechować w buforze statycznym). Dodatkowo klasa przechowuje wskaźnik do mechanizmu umożliwiającego identyfikację i rzutowanie tego obiektu na właściwy typ.
Gdy przypisujemy wartość do std::any
, obiekt ten tworzy wewnętrzną kopię tej wartości, zapewniając własność i zarządzanie czasem życia przechowywanego obiektu.
std::any::has_value()
— zwraca true
, jeśli std::any
zawiera wartość.std::any::reset()
— usuwa przechowywaną wartość, czyści obiekt.std::any::type()
— zwraca obiekt std::type_info
opisujący typ przechowywany w std::any
. Jeśli jest pusty, zwraca typeid(void)
.std::any_cast<T>(any)
— rzutuje zawartość std::any
na typ T
. Jeśli typ się nie zgadza, rzuca wyjątek std::bad_any_cast
.std::any_cast<T>(&any)
— zwraca wskaźnik do wartości typu T
lub nullptr
, jeśli typ się nie zgadza.#include <iostream>
#include <any>
#include <string>
int main() {
std::any a;
a = 42;
std::cout << "Wartość int: " << std::any_cast<int>(a) << std::endl;
a = std::string("Witaj, świecie");
if (a.type() == typeid(std::string)) {
std::cout << "Wartość string: " << std::any_cast<std::string>(a) << std::endl;
}
try {
std::cout << "Próba złego rzutowania: ";
std::cout << std::any_cast<int>(a) << std::endl; // rzutowanie na int, ale a zawiera string
} catch (const std::bad_any_cast &e) {
std::cout << "Błąd: " << e.what() << std::endl;
}
}
W grach std::any
może być używany do implementacji systemów atrybutów, gdzie różne cechy postaci lub obiektów mają różne typy danych. Dzięki temu można dynamicznie dodawać nowe atrybuty bez konieczności zmiany kodu klasy.
Przykład prostego systemu atrybutów:
#include <iostream>
#include <any>
#include <unordered_map>
#include <string>
struct Character {
std::unordered_map<std::string, std::any> attributes;
};
void printAttribute(const std::string &name, const std::any &value) {
if (value.type() == typeid(int)) {
std::cout << name << ": " << std::any_cast<int>(value) << std::endl;
} else if (value.type() == typeid(float)) {
std::cout << name << ": " << std::any_cast<float>(value) << std::endl;
} else if (value.type() == typeid(std::string)) {
std::cout << name << ": " << std::any_cast<std::string>(value) << std::endl;
} else {
std::cout << name << ": typ nieznany" << std::endl;
}
}
int main() {
Character hero;
hero.attributes["health"] = 100;
hero.attributes["mana"] = 75.5f;
hero.attributes["name"] = std::string("Wojownik");
for (const auto &attr : hero.attributes) {
printAttribute(attr.first, attr.second);
}
}
std::any
jest wolniejsze niż typowane kontenery (np. std::variant
) ze względu na dynamiczne alokacje i mechanizmy RTTI.std::any
może mieć większy rozmiar pamięciowy niż przechowywany obiekt.
std::any
to elastyczne narzędzie pozwalające na przechowywanie i manipulację obiektami dowolnego typu w czasie wykonania programu. Jest szczególnie użyteczne w sytuacjach, gdy nie znamy z góry wszystkich typów danych lub chcemy maksymalnej elastyczności. W programowaniu gier przydaje się do implementacji systemów atrybutów, eventów i innych mechanizmów wymagających przechowywania heterogenicznych danych.
Mimo pewnych ograniczeń wydajnościowych, stosowanie std::any
może znacząco uprościć kod i uczynić go bardziej uniwersalnym. Zalecam jego stosowanie tam, gdzie elastyczność jest ważniejsza niż maksymalna szybkość działania.