C++17 详解 19 — std::any

本文为 《C++17 in detail》 一书的中文渣中渣译文,不足之处还望指正。

翻译太特么累人了……剩余部分还是只做摘要翻译吧。

8. std::any

std::optional 可以表示一个二态值:空值、非空值;

std::variant 表示一个指定类型集合内的类型;

std::any 更进一步,它可以存储任何类型。

综合用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Chapter Any/any_demo.cpp
std::any a(12);

// 设置值:
a = std::string("Hello!");
a = 16;

// 读取值
// 按 int 读取:
std::cout << std::any_cast<int>(a) << '\n';

// 不能按 string 读取:
try
{
std::cout << std::any_cast<std::string>(a) << '\n';
}
catch(const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}

// reset 然后检查 any 是否存储了某个值:
a.reset();
if (!a.has_value())
{
std::cout << "a is empty!" << '\n';
}

// 可以把 any 用在容器中:
std::map<std::string, std::any> m;
m["integer"] = 10;
m["string"] = std::string("Hello World");
m["float"] = 1.0f;

for (auto &[key, val] : m)
{
if (val.type() == typeid(int))
std::cout << "int: " << std::any_cast<int>(val) << '\n';
else if (val.type() == typeid(std::string))
std::cout << "string: " << std::any_cast<std::string>(val) << '\n';
else if (val.type() == typeid(float))
std::cout << "float: " << std::any_cast<float>(val) << '\n';
}

代码输出:

1
2
3
4
5
6
16
bad any_cast
a is empty!
float: 1
int: 10
string: Hello World

上边的例子向我们揭示了一些事实:

  • std::any 跟 std::variant、std::optional 不同,它不是模版类。
  • std::any 默认不包含任何值,可以通过 .has_value() 检查是否有值。
  • 可以通过 .reset() 重置 std::any 对象。
  • std::any 基于“退化了的”类型运行——所以在赋值、初始化或 emplace 之前类型会通过 std::decay 转换。
  • 当使用一个其它类型赋值时,当前类型会被销毁。
  • 可以使用 std::any_cast<T> 函数访问值,如果当前类型跟 T 不同会抛出 bad_any_cast 异常。
  • 可以通过 .type() 获取当前类型,它返回当前类型的 std::type_info。

8.1 创建

  1. std::in_place_type 原地构造
1
2
std::any a4{std::in_place_type<MyType>, 10, 11};
std::any a5{std::in_place_type<std::string>, "Hello World"};
  1. std::make_any
1
std::any a6 = std::make_any<std::string>("Hello World");

8.2 修改值

  1. emplace
1
2
3
a.emplace<float>(100.5f);
a.emplace<std::vector<int>>({10, 11, 12, 13});
a.emplace<MyType>(10, 11);
  1. std::any_cast
1
std::any_cast<MyType&>(var).a = 11;
1
2
int* p = std::any_cast<MyType>(&var);
p->a = 12;

std::any_cast 有多个重载版本,如上两个,可以返回持有类型的指针或引用。

8.3 访问存储值

只能通过 std::any_cast 访问 std::any。

8.4 性能 & 内存考量

std::any 因为意图存储任意类型,需要的内存空间是不确定的,所以需要动态分配内存。为了尽量避免动态内存分配,std::any 跟 std::string 一样采用了 SBO(Small Buffer Optimization),即预先分配若干字节的栈空间,仅当存储类型尺寸大于栈空间时才进行动态内存分配。

评论