std::any
En general, C++
un lenguaje ligado a tipos y con seguridad de tipos. **El objeto de valor se declara como un cierto tipo,** este tipo define todas las operaciones posibles y también define el comportamiento del objeto. Además, un objeto no puede cambiar su propio tipo.
std::any
Es un tipo de valor que puede cambiar su propio tipo al tiempo que garantiza la seguridad del tipo . Es decir, ** puede contener cualquier tipo de valor y sabe qué tipo de valor tiene actualmente. ** No es necesario especificar todos los tipos posibles al declarar un objeto de este tipo. En términos sencillos, es posible verificar si un valor está almacenado y recuperar y convertir el valor almacenado de forma segura.
La clave de la implementación es que std::any
el objeto contiene tanto el valor como el tipo del valor. Debido a que el valor contenido puede tener un tamaño arbitrario, la memoria se puede asignar en el montón . Sin embargo, las implementaciones deben tratar de evitar la asignación de memoria para tipos pequeños, como int
en el montón.
Para un std::any
objeto, si asigna una cadena, asignará memoria y copiará la cadena, y almacenará el registro de que el valor actual es una cadena. Posteriormente, se puede usar una verificación de tiempo de ejecución para determinar el tipo del valor actual. Para convertir el valor actual a un tipo real , debe usarse any_cast<>
.
Como std::optional<>、std::variant<>
, std::any
los objetos tienen semántica de valor . Es decir, la copia se implementa como una copia profunda , que crea un objeto separado que contiene el valor actual en su propia memoria. Debido a que se puede utilizar la memoria de almacenamiento dinámico, la sobrecarga std::any
de la copia suele ser grande . Es más recomendable pasar objetos por referencia, o move
valores. Se admite la semántica std::any
parcial .move
usarstd::any
El siguiente ejemplo demuestra std::any
las competencias básicas:
#include <any>
#include <iostream>
#include <string>
using namespace std;
void judgeTypePrint(const std::any &a) {
if (a.type() == typeid(std::string)) {
std::string s = std::any_cast<std::string>(a);
cout << "using string " << s << endl;
} else if (a.type() == typeid(int)) {
cout << "using int " << endl;
}
}
int main() {
std::any a; // a为空
std::any b = 4.3; // b有类型为double的值4.3
a = 42; // a有类型为int的值42
b = std::string{
"hi"}; // b有类型为std::string的值"hi"
judgeTypePrint(a);
judgeTypePrint(b);
}
La salida es la siguiente:
using int
using string hi
Puede declarar uno vacío std::any
o inicializarlo con un valor de algún tipo. Si se pasa un valor inicial, std::any
el tipo del valor contenido se convierte en el tipo del valor inicial . Mediante el uso de funciones miembro type()
, puede comprobar si el tipo de valor incrustado es el mismo que un ID de un tipo determinado . Si el objeto es nulo, el ID de tipo será igual a typeid(void)
.
Para acceder al valor interno, debe std::any_cast<>
convertirlo al tipo real usando:
auto s = std::any_cast<std::string>(a);
Si la conversión falla, quizás porque el objeto es nulo o no coincide con el tipo del valor interno , se lanza una std::bad_any_cast
excepción . Por lo tanto, sin conocer el tipo actual, será mejor que lo use así:
try {
auto s = std::any_cast<std::string>(a);
...
}
catch (std::bad_any_cast& e) {
std::cerr << "EXCEPTION: " << e.what() << '\n';
}
El código de preprocesamiento es el siguiente:
std::any a = std::any();
try
{
std::basic_string<char> s = std::any_cast<std::basic_string<char> >(a);
} catch(std::bad_any_cast & e) {
std::operator<<(std::operator<<(std::operator<<(std::cerr, "EXCEPTION: "), e.what()), '\n');
}
Tenga en cuenta std::any_cast<>
que se crea un objeto del tipo especificado. Por ejemplo, en este ejemplo, si usa std::string
como std::any_cast<>
parámetro de plantilla, creará una cadena temporal ( prvalue
), y luego usará la cadena temporal para inicializar el nuevo objeto s
. Si no necesita inicializar otras variables, se recomienda convertir a un tipo de referencia para evitar crear un objeto temporal :
std::cout << std::any_cast<const std::string&>(a);
Si desea modificar el valor actual, también debe convertir al tipo de referencia correspondiente:
std::any_cast<std::string&>(a) = "world";
También puede std::any
llamar a la dirección de un objeto std::any_cast
. En este caso, el resultado de la conversión será un puntero del tipo correspondiente si los tipos coinciden, de lo contrario devolverá nullptr
:
#include <any>
#include <iostream>
#include <string>
using namespace std;
int main() {
std::any a; // a为空
std::any b = 4.3; // b有类型为double的值4.3
a = 42; // a有类型为int的值42
b = std::string{
"hi"}; // b有类型为std::string的值"hi"
auto p = std::any_cast<std::string>(&a);
if (p) {
cout << " any_cast ok " << endl;
} else {
cout << " any_cast failed " << endl;
}
auto p1 = std::any_cast<std::string>(&b);
if (p1) {
cout << " any_cast ok " << endl;
} else {
cout << " any_cast failed " << endl;
}
}
El código de preprocesamiento es el siguiente:
std::any a = std::any();
std::any b = std::any(4.2999999999999998);
a.operator=(42);
b.operator=(std::basic_string<char, std::char_traits<char>, std::allocator<char> >{
"hi", std::allocator<char>()});
std::basic_string<char, std::char_traits<char>, std::allocator<char> > * p = std::any_cast<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(&a);
if(p) {
std::operator<<(std::cout, " any_cast ok ").operator<<(std::endl);
} else {
std::operator<<(std::cout, " any_cast failed ").operator<<(std::endl);
}
std::basic_string<char, std::char_traits<char>, std::allocator<char> > * p1 = std::any_cast<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(&b);
if(p1) {
std::operator<<(std::cout, " any_cast ok ").operator<<(std::endl);
} else {
std::operator<<(std::cout, " any_cast failed ").operator<<(std::endl);
}
if
Alternativamente, se puede usar la nueva declaración con inicialización:
if (auto p = std::any_cast<std::string>(&a); p != nullptr) {
...
}
o:
if (auto p = std::any_cast<std::string>(&a)) {
...
}
Para borrar un std::any
objeto, puede llamar a:
a.reset(); // 清空对象
o:
a = std::any{
};
o:
a = {
};
También puede verificar directamente si un objeto tiene un valor:
if (a.has_value()) {
...
}
Tenga en cuenta que los tipos degeneran al almacenar valores (las matrices se convierten en punteros, las referencias de nivel superior y const
se ignoran). Para los literales de cadena, el tipo de valor será const char*
. Para std::any_cast<>
convertir usando , debe especificar el tipo explícitamente:
std::any a = "hello"; // type()是const char*
if (a.type() == typeid(const char*)) {
// true
...
}
if (a.type() == typeid(std::string)) {
// false
...
}
std::cout << std::any_cast<const char*>(a) << '\n'; // OK
std::cout << std::any_cast<std::string>(a) << '\n'; // EXCEPTION
Estas son básicamente std::any
todas las operaciones soportadas. No se definen operadores de comparación (lo que significa que no puede comparar ni ordenar objetos). No se define ninguna función hash, ni value()
funciones miembro definidas. Además, dado que el tipo solo está disponible en tiempo de ejecución , los genéricos no se pueden usar lambda
para manipular el valor actual independientemente del tipo. Solo puede usar std::any_cast<>
funciones de tiempo de ejecución para tratar con el valor actual, lo que significa que necesita algún código específico de tipo para trabajar con el C++
sistema de tipo reentrante cuando trata con el valor actual.
Sin embargo, puede std::any
colocar objetos en contenedores. Por ejemplo:
std::vector<std::any> v;
v.push_back(42);
std::string s = "hello";
v.push_back(s);
for (const auto& a : v) {
if (auto pa = std::any_cast<const std::string>(&a); pa != nullptr) {
std::cout << "string: " << *pa << '\n';
}
else if (auto pa = std::any_cast<const int>(&a); pa != nullptr) {
std::cout << "int: " << *pa << '\n';
}
}
Tenga en cuenta que siempre debe usar cadenas como esta if-else
. Las declaraciones no se pueden utilizar aquí switch
.
tipo y funcionamiento
tipo
En los archivos de encabezado, C++
la biblioteca estándar define las clases de la siguiente manera std::any
:
namespace std {
class any;
}
Es decir, std::any
no es una clase de plantilla en absoluto .
Además, se definen los siguientes tipos y objetos:
- Clase de excepción
std::bad_any_cast
, **Esta excepción se lanzará cuando falle la conversión. ** Esta clase deriva destd::bad_cast
, que a su vez deriva destd::exception
.std::any
Las clases también usan objetos (de tipo ) definidos<utility>
en archivos de encabezado. Si intentamos recuperar un tipo incorrecto de un objeto, se lanzará una excepción. Además, si el objeto no almacena ningún valor, también se lanzará una excepción. Por lo tanto, debe tener cuidado al usar funciones, y es mejor usar funciones para verificar si un objeto contiene un valor.std::in_place_type
std::in_place_type_t
std::any
std::bad_any_cast
std::any
std::bad_any_cast
std::any_cast
std::any::has_value
std::any
funcionar
La tabla de operaciones std::any
enumera std::any
todas las operaciones:
funcionar | Efecto |
---|---|
Constructor | Cree un objeto cualquiera (posiblemente llamando al constructor del tipo subyacente) |
make_any<>() |
Crear cualquier objeto (pasar parámetros para inicializar) |
incinerador de basuras | destruir cualquier objeto |
= |
asignar nuevo valor |
emplace<T>() |
asigna un T nuevo valor a un tipo |
reset() |
Destruye cualquier tipo (hace que el objeto quede vacío) |
has_value() |
Devuelve si el objeto tiene un valor |
type() |
std::type_info Devuelve el tipo actual como un objeto |
any_cast<T>() |
Convierte el valor actual a T un valor de tipo (lanza/devuelve si el tipo es incorrecto nullptr ) |
swap() |
intercambia los valores de dos objetos |
Constructor
La declaración del constructor es la siguiente:
constexpr any() noexcept;
any( const any& other );
any( any&& other ) noexcept;
template< class ValueType >
any( ValueType&& value );
template< class ValueType, class... Args >
explicit any( std::in_place_type_t<ValueType>, Args&&... args );
template< class ValueType, class U, class... Args >
explicit any( std::in_place_type_t<ValueType>, std::initializer_list<U> il,
Args&&... args );
De forma predeterminada, std::any
se inicializa para estar vacío.
std::any a1; // a1是空的
Si se inicializa por valor, el tipo del valor incrustado será su tipo degenerado :
std::any a2 = 42; // a2包含int类型的值
std::any a3 = "hello"; // a2包含const char*类型的值
Para hacer que el tipo del valor interno sea diferente del tipo del valor inicial , debe usar in_place_type
la notación:
std::any a4{
std::in_place_type<long>, 42};
std::any a5{
std::in_place_type<std::string>, "hello"};
El tipo aprobado in_place_type
también puede degenerar. El siguiente código declara un const char*
objeto retenido:
std::any a5b{
std::in_place_type<const char[6]>, "hello"};
Para inicializar std::any
un objeto con múltiples parámetros, debe crear manualmente el objeto o puede agregarlo std::in_place_type
como primer parámetro (ya que el tipo incrustado no se puede deducir directamente de múltiples valores iniciales):
std::any a6{
std::complex{
3.0, 4.0}};
std::any a7{
std::in_place_type<std::complex<double>>, 3.0, 4.0};
El código de preprocesamiento es el siguiente:
std::any a6 = std::any{
std::complex<double>{
3.0, 4.0}};
std::any a7 = std::any{
std::in_place_type_t<std::complex<double> >(std::in_place_type<std::complex<double> >), 3.0, 4.0};
Incluso puede pasar la columna inicial y otros parámetros:
// 用一个以lambda为排序准则的set初始化std::any对象
auto sc = [](int x, int y) {
return std::abs(x) < std::abs(y); };
std::any a8{
std::in_place_type<std::set<int, decltype(sc)>>,
{
4, 8, -7, -2, 0, 5},
sc};
cout<< a8.has_value()<<endl;
auto a = std::any_cast<std::set<int, decltype(sc)>>(a8);
El código de preprocesamiento es el siguiente:
class __lambda_12_15
{
public:
inline /*constexpr */ bool operator()(int x, int y) const
{
return abs(x) < abs(y);
}
using retType_12_15 = bool (*)(int, int);
inline constexpr operator retType_12_15 () const noexcept
{
return __invoke;
};
private:
static inline /*constexpr */ bool __invoke(int x, int y)
{
return __lambda_12_15{
}.operator()(x, y);
}
public:
// inline /*constexpr */ __lambda_12_15 & operator=(const __lambda_12_15 &) /* noexcept */ = delete;
// inline /*constexpr */ __lambda_12_15(const __lambda_12_15 &) noexcept = default;
};
__lambda_12_15 sc = __lambda_12_15{
};
std::any a8 = std::any{
std::in_place_type_t<std::set<int, __lambda_12_15, std::allocator<int> > >(std::in_place_type<std::set<int, __lambda_12_15, std::allocator<int> > >), std::initializer_list<int>{
4, 8, -7, -2, 0, 5}, sc};
std::cout.operator<<(a8.has_value()).operator<<(std::endl);
std::set<int, __lambda_12_15, std::allocator<int> > a = std::any_cast<std::set<int, __lambda_12_15, std::allocator<int> > >(a8);
Tenga en cuenta que también hay una función de acceso directo make_any<>()
, que puede aceptar uno o más parámetros (no es necesario utilizar in_place_type
parámetros). Debe especificar explícitamente el tipo de inicialización (incluso si solo hay un parámetro, no deducirá automáticamente el tipo):
auto a10 = std::make_any<float>(3.0);
auto a11 = std::make_any<std::string>("hello");
auto a13 = std::make_any<std::complex<double>>(3.0, 4.0);
auto a14 = std::make_any<std::set<int, decltype(sc)>> ({
4, 8, -7, -2, 0, 5}, sc);
A continuación se muestra un ejemplo completo:
#include <any>
#include <boost/core/demangle.hpp> //需要添加boost 库
#include <initializer_list>
#include <iostream>
#include <memory>
#include <set>
#include <string>
#include <utility>
struct A {
int age;
std::string name;
double salary;
#if __cpp_aggregate_paren_init < 201902L
// Required before C++20 for in-place construction
A(int age, std::string name, double salary)
: age(age), name(std::move(name)), salary(salary) {
}
#endif
};
//使用abi demangle打印任意持有的实例的良好类型名称
void printType(const std::any& a) {
std::cout << boost::core::demangle(a.type().name()) << '\n';
}
int main() {
std::any a1{
7};
std::any a2(std::in_place_type<A>, 30, "Ada", 1000.25);
auto lambda = [](auto&& l, auto&& r) {
return l.age < r.age; };
std::any a3(
std::in_place_type<std::set<A, decltype(lambda)>>,
{
A{
39, std::string{
"Ada"}, 100.25}, A{
20, std::string{
"Bob"}, 75.5}},
lambda);
printType(a1);
printType(a2);
printType(a3);
}
El código de preprocesamiento es el siguiente:
std::any a1 = std::any{
7};
std::any a2 = std::any(std::in_place_type_t<A>(std::in_place_type<A>), 30, "Ada", 1000.25);
class __lambda_29_19
{
public:
template<class type_parameter_0_0, class type_parameter_0_1>
inline /*constexpr */ auto operator()(type_parameter_0_0 && l, type_parameter_0_1 && r) const
{
return l.age < r.age;
}
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline /*constexpr */ bool operator()<const A &, const A &>(const A & l, const A & r) const
{
return l.age < r.age;
}
#endif
private:
template<class type_parameter_0_0, class type_parameter_0_1>
static inline /*constexpr */ auto __invoke(type_parameter_0_0 && l, type_parameter_0_1 && r)
{
return __lambda_29_19{
}.operator()<type_parameter_0_0, type_parameter_0_1>(l, r);
}
public:
// inline /*constexpr */ __lambda_29_19 & operator=(const __lambda_29_19 &) /* noexcept */ = delete;
// inline /*constexpr */ __lambda_29_19(const __lambda_29_19 &) noexcept = default;
};
__lambda_29_19 lambda = __lambda_29_19{
};
std::any a3 = std::any(std::in_place_type_t<std::set<A, __lambda_29_19, std::allocator<A> > >(std::in_place_type<std::set<A, __lambda_29_19, std::allocator<A> > >), std::initializer_list<A>{
A{
39, std::basic_string<char>{
"Ada", std::allocator<char>()}, 100.25}, A{
20, std::basic_string<char>{
"Bob", std::allocator<char>()}, 75.5}}, lambda);
El resultado de la operación es el siguiente:
int
A
std::set<A, main::{
lambda(auto:1&&, auto:2&&)#1}, std::allocator<A> >
valor modificado
emplace()
Los valores se pueden modificar mediante operadores de asignación y . Por ejemplo:
std::any a;
a = 42; // a含有int类型的值
a = "hello"; // a含有const char*类型的值
a.emplace<std::string>("hello"); // a含有std::string类型的值
a.emplace<std::complex<double>>(4.4, 5.5); // a含有std::complex<double>类型的值
valor de acceso
Para acceder al valor incrustado, debe std::any_cast<>
convertirlo al tipo real usando . Por ejemplo, para convertir el valor a string
, tiene los siguientes métodos:
std::any_cast<std::string>(a); // 返回值的拷贝
std::any_cast<std::string&>(a); // 通过引用获取写权限
std::any_cast<const std::string&>(a); // 通过引用获取读权限
Si el ID de tipo es el mismo después de eliminar la referencia de nivel superior const
, entonces el tipo coincide. Si la conversión falla, std::bad_any_cast
se lanzará una excepción.
Para evitar el manejo de excepciones, puede pasar any
la dirección del objeto. Cuando la conversión falla debido a una falta de coincidencia de tipo, devuelve nullptr
:
if (auto sp{
std::any_cast<std::string>(&a)}; sp != nullptr) {
... // 使用*sp获取a的值的写权限
}
if (auto sp{
std::any_cast<const std::string>(&a)}; sp != nullptr) {
... // 使用*sp获取a的值的读权限
}
Tenga en cuenta que la conversión a una referencia aquí dará como resultado un error de tiempo de ejecución:
std::any_cast<std::string&>(&a); // 运行时错误
mover la semántica
std::any
move
La semántica también es compatible . Tenga en cuenta, sin embargo, que el tipo subyacente admite la semántica de copia. Es decir, los tipos de solo movimiento no se admiten como tipos de valor integrados. Puede que no sea obvio cuál es la mejor manera
de manejar la semántica, puede hacer esto:move
std::string s("hello, world!");
std::any a;
a = std::move(s); // 把s移进a
s = std::move(std::any_cast<std::string&>(a)); // 把a中的string移动到s
Tenga en cuenta que, como de costumbre, el objeto cuyo valor se eliminó está en un estado que aún es válido pero el valor no está definido . Por lo tanto, puede continuar a
usándolo como una cadena, siempre que no haga suposiciones sobre su valor. La siguiente declaración no generará "NIL"
, la cadena cuyo valor se elimina suele ser la cadena vacía (pero puede tener otros valores):
std::cout << (a.has_value() ? std::any_cast<std::string>(a) : std::string("NIL"));
Aviso:
s = std::any_cast<std::string>(std::move(a));
También funciona, pero requiere un movimiento adicional. Una conversión directa a una referencia de valor real no se compilará:
s = std::any_cast<std::string&&>(a); // 编译期error
Nota y llamada
a = std::move(s); // 把s移进a
Por el contrario, el siguiente código puede no funcionar (aunque es C+
un ejemplo en el estándar +):
std::any_cast<string&>(a) = std::move(s); // OOPS:a必须持有string
Este código solo funcionará si un valor de a
un tipo ya está contenido . std::string
De lo contrario, el elenco lanza una excepción antes de que asignemos el nuevo valor std::bad_any_cast
.