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.