[c++17] std::any 笔记
背景
一种很常见的背景是,需要表示未知类型的数据。 比如可能是用户提供的数据,比如是一个Cache的实现, value想支持任意类型的数据
对于这种场景,c语言的出身的开发者通常会使用void*来实现
1struct day {
2 // ...things...
3 void* user_data;
4};
5
6struct month {
7 std::vector<day> days;
8 void* user_data;
9};
10
了解cpp11的开发者可能会使用std::shared_ptr<void> 来实现
1struct day {
2 // ...things...
3 std::shared_ptr<void> user_data;
4};
5
6struct month {
7 std::vector<day> days;
8 std::shared_ptr<void> user_data;
9};
10
那么有没有更好的实现办法呢?是有的,c++17中提供了std::any
std::any含义
The class any describes a type-safe container for single values of any copy constructible type.
Tip重点是提供了类型安全。
std::any 用法示例
1#include <any>
2#include <iostream>
3
4int main()
5{
6 std::cout << std::boolalpha;
7
8 // any type
9 std::any a = 1;
10 std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
11 a = 3.14;
12 std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
13 a = true;
14 std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';
15
16 // bad cast
17 try
18 {
19 a = 1;
20 std::cout << std::any_cast<float>(a) << '\n';
21 }
22 catch (const std::bad_any_cast& e)
23 {
24 std::cout << e.what() << '\n';
25 }
26
27 // has value
28 a = 2;
29 if (a.has_value())
30 {
31 std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
32 }
33
34 // reset
35 a.reset();
36 if (!a.has_value())
37 {
38 std::cout << "no value\n";
39 }
40
41 // pointer to contained data
42 a = 3;
43 int* i = std::any_cast<int>(&a);
44 std::cout << *i << "\n";
45}
46
47
输出结果为:
1
2int: 1
3double: 3.14
4bool: true
5bad any_cast
6int: 2
7no value
83
std::any 相等性判断
- 两个std::any 都为空
- 两个std::any包含的值相等
两种情况符合一种即可认为相等
std::any overhead
Implementations are encouraged to avoid dynamic allocations for small objects, but such an optimization may only be applied to types for which std::is_nothrow_move_constructible returns true.
也就是说根据std::any contain的value的类型不同,是可能存在heap allocations的。。 此外,对于不存在heap allocations的small object,由于 Small buffer optimization 的存在,可能会更大。 这部分是和实现相关的,不妨实验一下
1
2#include <any>
3#include <array>
4#include <iostream>
5#include <vector>
6
7void *operator new(std::size_t count) {
8 std::cout << " allocating: " << count << " bytes" << std::endl;
9 return malloc(count);
10}
11
12void operator delete(void *ptr) noexcept {
13 std::puts("global op delete called");
14 std::free(ptr);
15}
16
17template <int Num> class Container { char _array[Num]; };
18
19int main() {
20 int x = 3;
21 std::any a = x;
22 std::cout << "size of any(int):" << sizeof(a) << std::endl;
23 // sizeof(a) == sizeof(any) ==16
24 void *pa = &x;
25 std::cout << "size of void*:" << sizeof(pa) << std::endl;
26 // sizeof(void*) =8
27 {
28 std::any a = std::any(Container<8>{});
29 std::cout << "size of Container<8> :" << sizeof(a) << std::endl;
30 }
31
32 {
33 std::any a = std::any(Container<9>{});
34 std::cout << "size of Container<9> :" << sizeof(a) << std::endl;
35 }
36
37 {
38 std::any a = std::any(Container<10>{});
39 std::cout << "size of Container<10> :" << sizeof(a) << std::endl;
40 }
41
42 {
43 std::any a = std::any(Container<11>{});
44 std::cout << "size of Container<11> :" << sizeof(a) << std::endl;
45 }
46 return 0;
47}
48
49
测试环境为ubuntu 20.4 lts, gcc9.3.0 64bit 机器
结果为
1size of any(int):16
2size of void*:8
3size of Container<8> :16
4 allocating: 9 bytes
5size of Container<9> :16
6global op delete called
7 allocating: 10 bytes
8size of Container<10> :16
9global op delete called
10 allocating: 11 bytes
11size of Container<11> :16
12global op delete called
13
14
可以看到 std::any的大小为16,要比void*的大小大一倍
当std::any包含的value的size大于8时,会发生动态内存分配
why not void* ?
- void*缺少类型信息,无法做法类型安全
- void*由于缺少类型信息,copy数据时容易出现错误
- void*缺少对执行object的所有权,不管理其生命周期
why not std::shared_ptr<void>?
std::shared_ptr <void> 是知道如何删除对应对象的 (但是只是在std::shared_ptr的controll block中包含了deleter信息,并不知道具体的类型)
1
2some_day.user_data = std::make_shared<std::string>("Hello, world!");
3// ...much later...
4some_day = some_other_day; // the object at which some_day.user_data _was_
5 // pointing is freed automatically
6
然而,shared_ptr<void>仍然不包含类型信息 此外,使用shared_ptr<void>会有更大的性能开销,开销来自于需要额外分配内存,参考std::shared_ptr 笔记
使用场景
- 尽量避免使用
- 如果类型未知时无法避免,不得不用void*,此时可以用std::any代替。但是要注意std::any可能有额外的性能开销,所以在性能敏感的情境下需要谨慎
- 如果是类型是某种之一,用std::variant,而不是std::any
- 如果类型为某种(或者为空),用std::optional,而不是std::any
- 如果类型是某个函数对象,签名固定,应该使用std::function,而不是std::any