Guía completa de C++17 - std::any del nuevo componente

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::anyEs 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::anyel 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 inten el montón.

Para un std::anyobjeto, 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::anylos 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::anyde la copia suele ser grande . Es más recomendable pasar objetos por referencia, o movevalores. Se admite la semántica std::anyparcial .move

usarstd::any

El siguiente ejemplo demuestra std::anylas 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::anyo inicializarlo con un valor de algún tipo. Si se pasa un valor inicial, std::anyel 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_castexcepció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::stringcomo 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::anyllamar 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);
  } 

ifAlternativamente, 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::anyobjeto, 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 constse 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::anytodas 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 lambdapara 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::anycolocar 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::anyno 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 de std::bad_cast, que a su vez deriva de std::exception. std::anyLas 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_typestd::in_place_type_tstd::anystd::bad_any_caststd::anystd::bad_any_caststd::any_caststd::any::has_valuestd::any

funcionar

La tabla de operaciones std::anyenumera std::anytodas 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 Tnuevo 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_infoDevuelve el tipo actual como un objeto
any_cast<T>() Convierte el valor actual a Tun 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::anyse 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_typela 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_typetambié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::anyun objeto con múltiples parámetros, debe crear manualmente el objeto o puede agregarlo std::in_place_typecomo 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_typepará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_castse lanzará una excepción.

Para evitar el manejo de excepciones, puede pasar anyla 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::anymoveLa 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 ausá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 aun tipo ya está contenido . std::stringDe lo contrario, el elenco lanza una excepción antes de que asignemos el nuevo valor std::bad_any_cast.

referencia

[1] std::any: cómo, cuándo y por qué

Supongo que te gusta

Origin blog.csdn.net/MMTS_yang/article/details/130773238
Recomendado
Clasificación