[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*的大小大一倍

Warning

当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

参考资料