C++17之std::any的具体使用

目录
  • 1. 使用std::any
  • 2. std::any类型和操作
    • 2.1 std::any的类型
    • 2.2 std::any操作

一般来说,c++是一种具有类型绑定和类型安全性的语言。值对象声明为具有特定类型,该类型定义哪些操作是可能的以及它们的行为方式。值对象不能改变它们的类型。

std: any是一种值类型,它能够更改其类型,同时仍然具有类型安全性。也就是说,对象可以保存任意类型的值,但是它们知道当前保存的值是哪种类型。在声明此类型的对象时,不需要指定可能的类型。

诀窍在于,对象同时拥有包含的值和使用typeid包含值的类型。因为这个值可以有任何大小,所以可以在堆上分配内存,鼓励实现避免小对象的动态分配。也就是说,如果分配一个字符串,对象将为该值分配内存并复制该字符串,同时也在内部存储分配的字符串。稍后,可以执行运行时检查来确定当前值的类型,并使用any_cast<该值的类型>获取值。

1. 使用std::any

下面的例子演示了std::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为空或由特定类型的值初始化。初始值的类型成为所包含值的类型。通过使用成员函数type(),可以根据任何类型的类型ID检查所包含值的类型。如果对象是空的,对象类型ID是typeid(void)。要访问包含的值,可以通过std::any_cast<对象类型>的方式:

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

如果转换失败,因为对象为空或包含的类型不匹配,则抛出std::bad_any_cast。因此,在不检查或不知道类型的情况下,最好实现以下功能:

try {
auto s = std::any_cast<std::string>(a);
...
}
catch (std::bad_any_cast& e) {
std::cerr << "EXCEPTION: " << e.what() << '\n';
}

注意,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";

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

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

例1:

#include <iostream>
#include <any>

int main()
{
    std::any i = 42;
    const auto ptr = std::any_cast<int>(&i);
    if (ptr)
    {
        std::cout << ptr << std::endl;
    }

    return 0;
}

结果如下:

要清空现有std::任何可以调用的对象:

方法1:a.reset(); // makes it empty

方法2:a = std::any{};

方法3: a = {};

可以直接检查对象是否为空:

if (a.has_value()) {
...
}

还要注意,值是使用衰减类型存储的(数组转换为指针,忽略顶层引用和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]) << '\n'; // OK
std::cout << std::any_cast<std::string>(v[1]) << '\n'; // EXCEPTION

std::any没有定义比较运算符(因此,不能比较或排序对象),没有定义hash函数,也没有定义value()成员函数。由于类型只在运行时才知道,所以不能使用泛型lambdas处理与类型无关的当前值。总是需要运行时函数std::any_cast<>来处理当前值。

然而,可以将std::任何对象放入容器中。

例2:

#include <iostream>
#include <vector>
#include <any>

int main()
{
    std::vector<std::any> v;
    v.push_back(42);
    std::string s = "hello";
    v.push_back(s);
    for (const auto& a : v) {
        if (a.type() == typeid(std::string)) {
            std::cout << "string: " << std::any_cast<const std::string&>(a) << '\n';
        }
        else if (a.type() == typeid(int)) {
            std::cout << "int: " << std::any_cast<int>(a) << '\n';
        }
    }
}

结果如下:

2. std::any类型和操作

本节详细描述std::any的类型和操作。

2.1 std::any的类型

在头文件<any>中,c++标准库定义了类std::any,如下所示:

namespace std {
class any;
}

也就是说,std::any根本不是类模板。

此外,定义了以下类型和对象:

如果类型转换失败,则抛出异常类型std::bad_any_cast,它派生自std::bad_cast,而std::bad_cast派生自std::exception。

any对象还可以使用<utility>中定义的对象std::in_place_type(类型为std::in_place_type_t)。

2.2 std::any操作

std::any操作

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

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"};

即使传递给in_place_type的类型也会衰减。下面的声明包含一个const char*:

std::any a5b{std::in_place_type<const char[6]>, "hello"};

要通过多个参数初始化可选对象,必须创建该对象或将std::in_place_type添加为第一个参数(不能推断包含的类型):

std::any a6{std::complex{3.0, 4.0}};
std::any a7{std::in_place_type<std::complex<double>>, 3.0, 4.0};

甚至可以传递一个初始化器列表,后面跟着附加的参数:

// initialize a std::any with a set with lambda as sorting criterion:
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};

注意,还有一个方便的函数make_any<>(),它可以用于单个或多个参数(不需要in_place_type参数)。必须显式指定初始化的类型(如果只传递一个参数,则不会推导出初始化的类型):

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. 访问值

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

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

在这里,如果转换失败,将抛出std::bad_any_cast异常。

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

#include <iostream>
#include <string>
#include <any>

int main()
{
    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;
    }

    return 0;
}

结果如下:

如果类型不匹配转换失败了,传递一个地址将会返回nullptr:

auto a = std::make_any<std::string>("hello");
std::any_cast<std::string>(&a) // write-access via pointer
std::any_cast<const std::string>(&a); // read-access via pointer

注意,这里转换到引用会导致运行时错误:

std::any_cast<std::string&>(&a); // RUN-TIME ERROR

3. 修改值

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

#include <iostream>
#include <string>
#include <any>
#include <complex>

int main()
{
    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

    return 0;
}

结果如下:

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

与通常的从对象移动的情况一样,在最后一次调用之后,所包含的值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异常。

到此这篇关于C++17之std::any的具体使用的文章就介绍到这了,更多相关C++17 std::any内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++17之std::visit的具体使用

    目录 1. 使用对象函数方式访问 2. 使用泛型Lambdas访问 3. 使用重载的Lambdas来访问 它们必须明确地为每种可能的类型提供函数调用操作符.然后,使用相应的重载来处理当前的备选项类型. 1. 使用对象函数方式访问 例1: #include <iostream> #include <variant> #include <string> struct MyVisitor { void operator()(double d) const { std::cou

  • C++17中的std::optional的具体使用

    直入主题 本篇之中,仅仅述及 std::optional ,其它和 variant 相关的话题以后再说吧. std::optional 也划入 variant 类别中,其实它还是谈不上可称为变体类型的,但新版本中的三大件(optional,any and variant)也可以归一类无妨. C++17 之前 在 C 时代以及早期 C++ 时代,语法层面支持的 nullable 类型可以采用指针方式: T* ,如果指针为 NULL (C++11 之后则使用 nullptr ) 就表示无值状态(em

  • C++17 使用 std::string_view避免字符串拷贝优化程序性能

    C++中std::string是日常Coding中经常使用的一个类,使用起来非常方便,但是也存在一些弊端. 如下代码,参数传递的过程发生了内存分配(Memory Allocation)和内存拷贝. void fun(const std::string& s) { std::cout << s << std::endl; } const char* ch = "hello world"; // bad way, expensive if the strin

  • C++17之std::any的具体使用

    目录 1. 使用std::any 2. std::any类型和操作 2.1 std::any的类型 2.2 std::any操作 一般来说,c++是一种具有类型绑定和类型安全性的语言.值对象声明为具有特定类型,该类型定义哪些操作是可能的以及它们的行为方式.值对象不能改变它们的类型. std: any是一种值类型,它能够更改其类型,同时仍然具有类型安全性.也就是说,对象可以保存任意类型的值,但是它们知道当前保存的值是哪种类型.在声明此类型的对象时,不需要指定可能的类型. 诀窍在于,对象同时拥有包含

  • C++17使用std::optional表示可能存在的值

    目录 前言 返回一个bool值 使用 std::optional 改写 总结 前言 平时写代码会遇到一种传递参数特殊值标记特殊流程,或者函数返回值存在魔法数的情况,很需要一种标记参数或返回值状态的结构,那么在 C++17 标准下提供了 std::optional 这个模板类,可以表示一个值不存在的状态,一起来看看用法吧. 返回一个bool值 以下例子纯属虚构,只为说明问题,无实际意义 bool getBoolVal(int a, int b) {     int* n = new int;  

  • C++ 11实现检查是否存在特定的成员函数

    问题提出 最近工作中遇到这样一个需求:实现一个ToString函数将类型T转换到字符串,如果类型T中含有同名方法ToString则直接调用. 这样一个ToString实现可以使用std::enable_if来做到,但是这里的难点在于如何判断类型T中存在这样一个ToString方法,以便可以放入enable_if中做SFINAE. 检查类中是否存在特定成员 相同的问题在知乎上有人提出过,@孙明琦的答案提供了一个用于检测特定检测子U在类型T下是否有效的检测器is_detected_v.其中用到了一个

  • C++实现俄罗斯方块游戏

    本文实例为大家分享了C++实现俄罗斯方块游戏的具体代码,供大家参考,具体内容如下 使用VS2017编译 思路: 1.打印出游戏地图,也就是设计游戏地图类game_interdace,包括设计游戏开始界面的函数,游戏地图界面的函数,游戏结束界面的函数,以及设计一些辅助函数如(设置光标为目标点,改变颜色,隐藏光标)来完成上述设计. 2.设计方块图形类,包括生成图形,清理图形,图形移动,图形停止的处理,图形的消失(实质是得分)等. #include <iostream> #include<co

  • C++ 多线程之互斥量(mutex)详解

    目录 std::mutex std::recursive_mutex std::time_mutex std::recursive_timed_mutex std::shared_mutex std::shared_timed_mutex 总结 C++ 11中的互斥量,声明在 <mutex> 头文件中,互斥量的使用可以在各种方面,比较常用在对共享数据的读写上,如果有多个线程同时读写一个数据,那么想要保证多线程安全,就必须对共享变量的读写进行保护(上锁),从而保证线程安全. 互斥量主要有四中类型

  • C++精要分析lambda表达式的使用

    目录 引言 语法与示例 捕获列表 捕获引用 特殊用法 实现原理 应用 引言 C++要走向现代语言,如果不支持lambda表达式,你很难认为这门语言和现代有什么关系.幸好,从C++11标准起,它就实现了对lambda表达式的支持. 那么,什么是lambda表达式呢? lambda表达式是匿名函数,就是说不用定义函数名,函数实现可以直接嵌入在业务逻辑代码中.诸如python.java.C#等语言,都将其作为基础特性. 其优点是提高了代码的可读性,对于一些无需重用的方法特别适合.例如在容器的迭代中实现

  • C语言中实现“17进制”转“10进制”实例代码

    C语言中实现"17进制"转"10进制"实例代码 17进制转成10进制,输入是数字跟大写字母.例如输入G.11.FF,分别输出16.18.270 #include <iostream> #include <string> using namespace std; int main() { string s; int len,factor=17,num; bool sign;//用来标志输入字符串是否非法 while(cin>>s)

随机推荐