C++17:std::variant/std::optional/std::any设计

文章目录

  • 前言
  • 稍微总结
  • std::variant
    • 动机和设计
    • 内部实现
      • boost
      • STL
  • std::optional
    • 动机和设计
    • 内部实现
      • boost
      • STL
  • std::any
    • 动机和设计
      • 转换
    • 内部实现
      • boost
      • STL

前言

2021年了。现在回顾一下c++17标准中的std::variant/std::optional/std::any三个库是怎么实现的。

稍微总结

这三个库基本都是实现可选语义

  • std::optional:存有T或者不存有值
  • std::any:存有任意类型值(可以看作带类型信息的void*)
  • std::variant:存有T或U值(可以看作带类型信息的union)

以上三种都可以看作是对union一种安全性改进。C/C++的union用法没有给程序员任何检查的手段,使得程序员心智负担加重。
另外这三个库的语义都很类似,但却分成了三个独立的库,且各自对同一种操作的写法也各不相同(丝毫没有减少心智负担):

std::optional<int> var1 = 7;
std::variant<int,string> var2 = 7;
std::any var3 = 7;

auto x1 = *var1 ;					 // 对 optional 解引用
auto x2 = std::get<int>(var2);       // 像访问 tuple 一样访问 variant
auto x3 = std::any_cast<int>(var3);  // 转换 any

三个库具体接口可以查阅cppreference。

std::variant

动机和设计

一个安全类型的union:在union之上加了静态类型检查,有效阻止未定义行为。此外它还允许有默认的空状态。

内部实现

boost

采用TR1标准,即没有不定模板参数的variant签名。

template <
      typename T0_
    , BOOST_VARIANT_ENUM_SHIFTED_PARAMS(typename T)
    >
class variant
{
	/*...*/
	which_t which_;
    storage_t storage_;
}

另外std::variant令人繁琐的实现是:在不同类型variant之间的拷贝构造要考虑多种情景(主要是为了zero overhead)。于是这里涉及了重载的idom:boost选择的策略是类内用SFINEA方式实现多个重载函数。

STL

在内存布局上和boost大同小异。实现上的不同在于重载的处理:STL则是通过继承类模板来实现静态时多态。

std::optional

动机和设计

函数返回值可能不在返回类型的范围之内。例如float sqrt(float)函数。如果我们输入一个负数,此时返回值是无定义的。我们需要额外一个空类型,来区分这些无定义输入的返回状态(也可以是处理无法预定的返回类型)。

内部实现

boost

Empty_byte {};
template<typename T>
class aligned_storage
{
  /*...*/
  enum _storage<typename T> {
    Empty_byte _empty;
    T _value;
  };
};
template<typename T>
class optional {
  /*...*/
  aligned_storage<T> storage;
};

STL

和boost实现基本一致。

std::any

动机和设计

C++是一种静态强类型语言。有时候我们需要用到一些“弱”类型变量:它可以视作一个性的类型,当我们需要时,可以安全地把它cast到一个我们想要的类型。

转换

在传统C++中,我们使用转换的方式有以下三种:

  • cast
int a = static_cast<int>(1.0);
string s = lexical_cast<string>(a);
  • 构造函数转换(转进)
class a {
public:
  a(){}
  a(int) {}
  a(const string&){}
}
  • 返回函数转换(转出)
class a {
public:
  operator int(){}
  operator char*(){}
}

我们可以利用std::any来统一类型的转换:

std::any a = string("1"); //转进
int i = std::any_cast<int>(a); //转出 i=1

内部实现

boost

实现一个模板构造函数。然后通过一个模板类来保存对应的值和类型(模拟虚表)。

class any {
public:
  /*
  ...
  */
  template<typename T>
  any(T&& t) { _storage = new storage_t<std::decay_t<T>>(std::forward<T>(t))}
 /*
 ...
 */
 struct p{};
 template<typename T> struct _storage_t:public p{ T t;};
 p *storage;
 };

STL

STL则是使用void*类型实现:

  1. 因为值使用void*,any需要额外保存一个RTTI信息(gcc则用了函数指针)
  2. 对小内存值进行了优化(SSO)

在某些角度下,std::any的设计可以与std::function进行比较。标准std::function-type擦除策略的规范不同,它强调了保证nothrow移动操作的价值。虽然std::function对可应用小对象优化的包装函数对象类型几乎没有限制,但在本方案中,std::any将这些情况限制在包含对象的复制/移动操作不能引发异常的情况下。(n3804)

引用:
Variant: a type-safe union for C++17 (v8)
A proposal to add a utility class to represent optional objects (Revision 3)
Any Library Proposal (Revision 3)
From Mechanism to Method
MS-STL
GCC
BOOST

你可能感兴趣的:(编程,c++,开发语言)