Modern C++ std::any的实现原理

1. 前言

std::any 是 C++17 中引入的一个新特性,它是一个类型安全的容器,可以在其中存储任何类型(但此类型必须可拷贝构造)的值,包括基本类型、自定义类型、指针等。相比于void* 指针,std::any 更为类型安全,可以避免由于类型转换错误而导致的运行时错误。

std::any 获取值时需要指定正确的类型。如果尝试获取的类型与存储的类型不匹配,将抛出 std::bad_any_cast 异常。

2. std::any初体验

cppreference上就有一个例子,copy过来供大家学习:

#include <any>
#include <iostream>
 
int main()
{
    std::cout << std::boolalpha;
 
    // any type
    std::any a = 1;
    std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
    a = 3.14;
    std::cout << a.type().name() << ": " << std::any_cast<double>(a) << '\n';
    a = true;
    std::cout << a.type().name() << ": " << std::any_cast<bool>(a) << '\n';
 
    // bad cast
    try
    {
        a = 1;
        std::cout << std::any_cast<float>(a) << '\n';
    }
    catch (const std::bad_any_cast& e)
    {
        std::cout << e.what() << '\n';
    }
 
    // has value
    a = 2;
    if (a.has_value())
        std::cout << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
 
    // reset
    a.reset();
    if (!a.has_value())
        std::cout << "no value\n";
 
    // pointer to contained data
    a = 3;
    int* i = std::any_cast<int>(&a);
    std::cout << *i << '\n';
}

输出:

int: 1
double: 3.14
bool: true
bad any_cast
int: 2
no value
3 

正如介绍中所说:

std::any可以有值,可以无值,还能存储int/double/bool等等类型数据,获取值时如果类型不对会抛出异常。

3. 原理-存储

any的存储是所有container中最简单的一个了,全部实现才600来行,通读一点问题没有。

any就是一个普通的类,没有任何模板参数,其 实现 在文件/usr/include/c++/11/any中。

class std::any {
  private:
    void (*_M_manager)(std::any::_Op, const std::any *, std::any::_Arg *);
    std::any::_Storage _M_storage;
}

union std::any::_Storage {
    void *_M_ptr;
    std::aligned_storage<8, 8>::type _M_buffer; //size与指针相同,要求8字节对齐
}

std::any只有两项数据成员:

  1. _M_storage:数据存在这。因为用到了小对象优化,所以是一个union,笼统的讲:小对象用栈空间_M_buffer, 大点的对象要在堆上分配空间,由_M_ptr指向,不过这么讲不是很准确。
  2. _M_manager:一个特殊的函数,用来操作_M_storage,操作类型包括:访问、克隆(针对any copy)、移动(针对move语义)等等。

本节着重于第一条_M_storage,第二条我们在后面的“原理-初始化” 和 “原理-获取数据 ”中会详细叙述。 

3.1 存储-直观认识

关于_M_storage的内容,我们先看两个实例,从例子中有个直观认识:

std::any a = 1;

_M_ptr=1肯定不合法,1是按字节存入了char _M_buffer[8], 我的机器因为是小端故第一个字节是\001. 所以这里用了小对象优化。

再给a赋值为一个字符串,看看8个字节存不下的情况:

a = string("mzhai");

 可以看到new出了新空间,在堆上存储了string “mzhai”,由_M_ptr指向它。

3.2 存储-源码

OK,让我们看看源码,它是如何判断用不用小对象优化的哪?

 94     template<typename _Tp, typename _Safe = is_nothrow_move_constructible<_Tp>,
 95          bool _Fits = (sizeof(_Tp) <= sizeof(_Storage))
 96               && (alignof(_Tp) <= alignof(_Storage))>
 97       using _Internal = std::integral_constant<bool, _Safe::value && _Fits>;

_Internal见名知意,用“内部”栈空间的意思,其值由两点确定:

  1. _Tp必须是可移动构造 且 不抛异常的。
  2. _Tp的size必须小于预留的栈空间大小(8字节),且对齐要求小于等于8。

4. 原理-初始化

现在我们已经知道了any有两个重要的成员变量 以及 数据存在哪。我们想进一步看看如何由别的数据类型初始化一个any对象哪?这两个成员变量都是如何赋的值哪?

除了默认构造函数还有四种方式初始化一个any对象:

any(_Tp&& __value) //调用_Tp copy ctor/move ctor
any(in_place_type_t<_Tp>, _Args&&... __args)  //调用_Tp parameterized ctor
any(in_place_type_t<_Tp>, initializer_list<_Up> __il, _Args&&... __args) //调用_Tp parameterized ctor

operator=(_Tp&& __rhs)

如果你不太熟悉前三种用法,请参考官方链接

一旦初始化了一个any对象,就可以用它的本身的copy ctor/move ctor来初始化别的any对象,所以上面这四个是基础。

前三个很相似,我是指它们的流程,流程分三步:

1. 它们都有激活的条件至少要求Tp是可拷贝构造的

(原因请参考《Modern C++ std::any为何要求Tp可拷贝构造?-CSDN博客

比如第一个要求:is_copy_constructible<_VTp>

后面的要求更多,不仅仅要求is_copy_constructible,在此不再展开介绍。

2. 初始化_M_manager它是一个函数指针,要么指向小对象处理函数_Manager_internal::_S_manage,要么指向非小对象处理函数_Manager_external::_S_manage。

    template <typename _Tp, typename _VTp = _Decay_if_not_any<_Tp>,
	      typename _Mgr = _Manager<_VTp>,
	      typename = _Require<__not_<__is_in_place_type<_VTp>>,
				  is_copy_constructible<_VTp>>>
      any(_Tp&& __value)
      : _M_manager(&_Mgr::_S_manage)

    template<typename _Tp, typename _Safe = is_nothrow_move_constructible<_Tp>,
	     bool _Fits = (sizeof(_Tp) <= sizeof(_Storage))
			  && (alignof(_Tp) <= alignof(_Storage))>
      using _Internal = std::integral_constant<bool, _Safe::value && _Fits>;

    template<typename _Tp>
      struct _Manager_internal; // uses small-object optimization

    template<typename _Tp>
      struct _Manager_external; // creates contained object on the heap

//根据前面一节"原理-存储"中介绍的判断来决定用不用小对象优化
    template<typename _Tp>
      using _Manager = conditional_t<_Internal<_Tp>::value,
				     _Manager_internal<_Tp>,
				     _Manager_external<_Tp>>;

无论_Manager_internal还是_Manager_external 都定义了四个相同的静态函数:

  1.  _S_manage(_Op __which, const any* __anyp, _Arg* __arg); 
  2. _S_create(_Storage& __storage, _Up&& __value)       //下面的“存入数据”会用到
  3. _S_create(_Storage& __storage, _Args&&... __args)  //下面的“存入数据”会用到
  4. _S_access(const _Storage& __storage)                      //any_cast会用到

我们的关注点在_S_manage,简单比较下_Manager_internal::_S_manage 与 _Manager_external::_S_manage的不同:

template<typename _Tp>    
    any::_Manager_internal<_Tp>::
    _S_manage(_Op __which, const any* __any, _Arg* __arg)
    {
      // 从char _M_storage._M_buffer[8]中取数据
      auto __ptr = reinterpret_cast<const _Tp*>(&__any->_M_storage._M_buffer);
      switch (__which)
      {
          case _Op_access:
	        __arg->_M_obj = const_cast<_Tp*>(__ptr);
        	break;

template<typename _Tp>
    void
    any::_Manager_external<_Tp>::
    _S_manage(_Op __which, const any* __any, _Arg* __arg)
    {
      // 从_M_storage._M_ptr指向的内存中取数据
      auto __ptr = static_cast<const _Tp*>(__any->_M_storage._M_ptr);
      switch (__which)
      {
          case _Op_access:
	        __arg->_M_obj = const_cast<_Tp*>(__ptr);
	        break;

3. 存入数据

any中的两大数据成员之一_M_manager已经搞定,下一步就是_M_storage。

_M_storage的初始化,无论是三种初始化中的哪种,都是委托给_Mgr::_S_create完成的。比如第一种:

      any(_Tp&& __value)
      : _M_manager(&_Mgr::_S_manage)
      {
	        _Mgr::_S_create(_M_storage, std::forward<_Tp>(__value));
      }

当然根据是否应用了SSO, _S_create也分两种情况:

1. _Manager_internal 调用了placement new在原有内存上构造Tp:

template<typename _Up>
	  static void
	  _S_create(_Storage& __storage, _Up&& __value)
	  {
	    void* __addr = &__storage._M_buffer;
	    ::new (__addr) _Tp(std::forward<_Up>(__value)); //调用copy ctor/move ctor
	  }

	template<typename... _Args>
	  static void
	  _S_create(_Storage& __storage, _Args&&... __args)
	  {
	    void* __addr = &__storage._M_buffer;
	    ::new (__addr) _Tp(std::forward<_Args>(__args)...); //调用parameterized ctor
	  }

 2. _Manager_external在堆上构造Tp

	template<typename _Up>
	  static void
	  _S_create(_Storage& __storage, _Up&& __value)
	  {
	    __storage._M_ptr = new _Tp(std::forward<_Up>(__value));
	  }
	template<typename... _Args>
	  static void
	  _S_create(_Storage& __storage, _Args&&... __args)
	  {
	    __storage._M_ptr = new _Tp(std::forward<_Args>(__args)...);
	  }

OK,前三种初始化讲完了,来看下第四种:assignement operator 

   template<typename _Tp>
      enable_if_t<is_copy_constructible<_Decay_if_not_any<_Tp>>::value, any&>
      operator=(_Tp&& __rhs)
      {
	*this = any(std::forward<_Tp>(__rhs));
	return *this;
      }

它先调用了第一种初始化方式构造了一个临时any对象,再调用any的mtor把数据move过去,move委托给了_Manager_**ternal::_S_manage

    any(any&& __other) noexcept
    {
      if (!__other.has_value())
	_M_manager = nullptr;
      else
	{
	  _Arg __arg;
	  __arg._M_any = this;
	  __other._M_manager(_Op_xfer, &__other, &__arg);
	}
    }

5. 原理-获取数据 

数据的获取必须通过any_cast,其语法如下:

无论是哪种形式,都会调用到__any_caster:

  template<typename _Tp>
    void* __any_caster(const any* __any)
    {
      // any_cast<T> returns non-null if __any->type() == typeid(T) and
      // typeid(T) ignores cv-qualifiers so remove them:
      using _Up = remove_cv_t<_Tp>;
      // The contained value has a decayed type, so if decay_t<U> is not U,
      // then it's not possible to have a contained value of type U:
      if constexpr (!is_same_v<decay_t<_Up>, _Up>)
	        return nullptr;
      // 又一次要求存储的类型必须拷贝可构造!
      else if constexpr (!is_copy_constructible_v<_Up>)
	        return nullptr;
      // First try comparing function addresses, which works without RTTI
      else if (__any->_M_manager == &any::_Manager<_Up>::_S_manage
#if __cpp_rtti
	  || __any->type() == typeid(_Tp)
#endif
	  )
	{
	  return any::_Manager<_Up>::_S_access(__any->_M_storage); //真正的访问数据
	}
      return nullptr;
    }

 当然根据是否用了SSO,依然有两种情况。代码比较简单,我就不解释了。

//_Manager_internal	 SSO
    static _Tp*
	_S_access(const _Storage& __storage)
	{
	  // The contained object is in __storage._M_buffer
	  const void* __addr = &__storage._M_buffer;
	  return static_cast<_Tp*>(const_cast<void*>(__addr));
	}

//_Manager_external
	static _Tp*
	_S_access(const _Storage& __storage)
	{
	  // The contained object is in *__storage._M_ptr
	  return static_cast<_Tp*>(__storage._M_ptr);
	}

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深山老宅

鸡蛋不错的话,要不要激励下母鸡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值