• C++笔记-std::any、void*和shared_ptr<void>


    参考资料:
    C++17之std::any https://blog.csdn.net/janeqi1987/article/details/100568181
    std::any: How, when, and why: https://devblogs.microsoft.com/cppblog/stdany-how-when-and-why/

    1.简介

    1.1 为什么不用void* ?

    struct day { 
      // ...things... 
      void* user_data; 
    }; 
    
    struct month { 
      std::vector<day> days; 
      void* user_data; 
    };
    

    a. void*不能保证类型安全,你可以将一个void * 赋给 Foo *,无论它指向的对象是否实际上是Foo类的

    some_day.user_data = new std::string{"Hello, World!"}; 
    // …much later 
    Foo* some_foo = static_cast<Foo*>(some_day.user_data); 
    some_foo->frobnicate(); // BOOM!
    

    b. void *不能像智能指针那样管理生命周期,因此客户端必须手动管理关联数据的生命周期。错误会导致内存泄漏

    delete some_day.user_data; 
    some_day.user_data = nullptr; 
    some_month.days.clear(); // Oops: hopefully none of these days had 
                             // non-null user_data
    

    c. 库无法复制void *指向的对象,因为它不知道对象的类型

    some_month.days[0] = some_month.days[1]; 
    if (some_month.days[1].user_data) { 
      // user_data内保存了一个string,如果不希望在days之间共享同一个string,需要手动copy
      std::string const& src = *some_month.days[1].user_data; 
      some_month.days[0].user_data = new std::string(src); 
    }
    

    1.2 为什么不用shared_ptr<void>

    使用shared_ptr<void>代替void*可以解决声明周期管理的问题。shared_ptr有足够的类型信息以了解如何正确销毁它指向的对象。

    struct day {
      // ...things...
      std::shared_ptr<void> user_data;
    };
    struct month {
      std::vector<day> days;
      std::shared_ptr<void> user_data;
    };
    ......
    some_day.user_data = std::make_shared<std::string>("Hello, world!");
    // ...much later...
    some_day = some_other_day; // the object at which some_day.user_data _was_
                               // pointing is freed automatically
    

    但是std::shared_ptr和void*一样不能解决类型安全的问题。
    使用shared_ptr 需要reinterpreting integral 为void *并直接存储它们来避免内存分配;使用shared_ptr强制我们甚至为诸如int之类的微小对象分配内存。

    1.3 C++17引入引入了std::any

    定义在any头文件中:#include <any>
    是一个可用于任何类型单个值的类型安全的容器.
    类型安全:每个对象在定义时被分配一个类型。对于一个程序或者程序的一部分,如果使用的对象符合它们规定的类型,那么它们是类型安全的。
    std: any是一种值类型,它能够更改其类型,同时仍然具有类型安全性。也就是说,对象可以保存任意类型的值,但是它们知道当前保存的值是哪种类型。在声明此类型的对象时,不需要指定可能的类型
     诀窍在于,对象同时拥有包含的值使用typeid包含值的类型。因为这个值可以有任何大小,所以可以在堆上分配内存,鼓励实现避免小对象的动态分配。也就是说,如果分配一个字符串,对象将为该值分配内存并复制该字符串,同时也在内部存储分配的字符串。稍后,可以执行运行时检查来确定当前值的类型,并使用any_cast<该值的类型>获取值。

    #include <typeinfo.h>
    #include <any>
    
    std::any a; // a is empty
    std::any b = 4.3; // b has value 4.3 of type double
    
    a = 42; // a has value 42 of type int
    b = std::string{"hi"}; // b has value "hi" of type std::string
    
    if (a.type() == typeid(std::string)) {
        std::string s = std::any_cast<std::string>(a);
        useString(s);
    }
    else if (a.type() == typeid(int)) {
        useInt(std::any_cast<int>(a));
    }
    

    小结
    std::any a = 1;: 声明一个any类型的容器,容器中的值为int类型的1
    a.type(): 得到容器中的值的类型
    std::any_cast(a);: 强制类型转换, 转换失败可以捕获到std::bad_any_cast类型的异常
    has_value(): 判断容器中是否有值
    reset(): 删除容器中的值
    std::any_cast(&a): 强制转换得到容器中的值的地址

    2.std::any操作

    函数 说明
    constructors 创建一个any对象(可能调用底层类型的构造函数)
    make_any() 创建一个any对象(传递值来初始化它)
    destructor 销毁any对象
    = 分配一个新值
    emplace() 分配一个类型为T的新值
    reset() 销毁any对象的值(使对象为空)
    has_value() 返回对象是否具有值
    type() 返回当前类型为std::type_info对象
    any_cast() 使用当前值作为类型T的值(如果其他类型除外)
    swap() 交换两个any对象的值

    2.1 构造函数

    默认情况下,std::any的初始值为空。

    std::any a1; // a1 is empty
    如果传递一个值进行初始化,则将其衰减类型用作所包含值的类型:

    std::any a2 = 42; // a2 contains value of type int
    std::any a3 = "hello"; // a2 contains value of type const char*
    

    要保存与初始值类型不同的类型,必须使用in_place_type标记:

    std::any a4{std::in_place_type<long>, 42};
    std::any a5{std::in_place_type<std::string>, "hello"};
    

    make_any<>(),可以用于单个或多个参数(不需要in_place_type参数)。必须显式指定初始化的类型(如果只传递一个参数,则不会推导出初始化的类型):
    注:std::make_any会新建对象

    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);
    

    2.2 访问值

    要访问包含的值,必须使用std::any_cast<>将其转换为其类型。将该值转换为一个字符串,有几个选项:

    std::any_caststd::string(a) // yield copy of the value
    std::any_caststd::string&(a); // write value by reference
    std::any_cast<const std::string&>(a); // read-access by reference

    如果把std::any中所包含的类型转换为移除了传递类型的顶层引用后的类型ID,则转换类型是适合的。如下:

    const auto& s = std::make_any<std::string>("hello");    
    if (s.type() == typeid(std::string)) { //删除顶层cosnt和引用后的类型
        auto a = std::any_cast<std::string>(s);
        std::cout << a << std::endl;
    }
    

    2.3 修改值

    相应的赋值和emplace()操作。例如:

    std::any a;
    a = 42; // a contains value of type int
    a = "hello"; // a contains value of type const char* 
    a.emplace<std::string>("hello world");// a contains value of type std::string
    

    2.4 移动语法

    std: any也支持移动语义。但是,请注意,move语义必须满足包含的类型具有可复制构造函数。也就是说,不支持只移动类型作为包含值类型。处理move语义的最佳方法可能并不明显。所以,你应该这样做:

    std::string s("hello, world!");
    std::any a;
    a = std::move(s); // move s into a
    s = std::move(std::any_cast<string&>(a)); // move assign string in a to s
    //这种可能有问题,只有当包含的值已经是字符串时,才可以这样做。如果没有,转换将抛出一个std::bad_any_cast异常。
    std::string s3 = std::any_cast<std::string&>(std::move(a));
    std::cout << s3 << std::endl;
    

    与通常的从对象移动的情况一样,在最后一次调用之后,所包含的值a是未指定的。因此,可以使用a作为字符串,只要没有对所包含的字符串值的值做任何假设。

    注意

    s = std::any_cast<string>(std::move(a));
    

    也可以,但需要一个额外的移动。然而,以下内容是危险的(尽管它是c++标准中的一个例子):

    std::any_cast<string&>(a) = std::move(s2); // OOPS: a to hold a string
    

    只有当包含的值已经是字符串时,才可以这样做。如果没有,转换将抛出一个std::bad_any_cast异常。

    注意:
    std::any_cast<>创建了一个传递类型的对象。如果将std::string作为模板参数传递给std::any_cast<>,它将创建一个临时string(一个prvalue),然后用它初始化新对象s。如果没有这样的初始化,通常最好转换为引用类型,以避免创建临时对象:

    std::cout << std::any_cast<const std::string&>(a);
    

    要修改该值,需要转换为对应的引用类型:

    std::any_cast<std::string&>(a) = "world";
    

    注意2:
    可以为std::any对象的地址调用std::any_cast。在这种情况下,如果类型匹配,则强制转换返回相应的地址指针;如果不匹配,则返回nullptr:

    auto p = std::any_cast<std::string>(&a);
    if (p) {
       ...
    }
    

    注意3:
    是使用衰减类型存储的(数组转换为指针,忽略顶层引用和const)。对于字符串常量,这意味着值类型是const char*。要检查type()并使用std::any_cast<>,必须使用以下类型:

    std::any a = "hello"; // type() is const char*
    if (a.type() == typeid(const char*)) { // true
      ...
    }
    if (a.type() == typeid(std::string)) { // false
      ...
    }
    std::cout << std::any_cast<const char*>(v[1]) << '
    '; // OK
    std::cout << std::any_cast<std::string>(v[1]) << '
    '; // EXCEPTION
    
  • 相关阅读:
    XAML中的戏法
    提前预览Visual Studio 2010
    大道至简
    Windows Service下的MessageBox
    WPF中使用Expression Encoder SDK开发流媒体
    使用latex
    [zz]2D动画制作工具比较
    android platform_frameworks_base
    Fast and easy high resolution fractals with a pixel shader
    Bézier Surface
  • 原文地址:https://www.cnblogs.com/gnivor/p/12793239.html
Copyright © 2020-2023  润新知