std::any源码

std::any源码分析

正常思路下,我们在实现std::any时需要保存data和type info用以any_cast成正确的类型,但存储空间占用较多。那么我们如何做到type map呢?在实现上可以使用lambdas来做,不过gcc的实现是通过any类中的manger模板类的static方法实现的。

std::any构成

sizeof(std::any) = 16,有一个函数指针和一个_Storage成员组成.

class any {
	// ...
	// Holds either pointer to a heap object or the contained object itself.
	union _Storage {
	  constexpr _Storage() : _M_ptr{nullptr} {}
	
	  // Prevent trivial copies of this type, buffer might hold a non-POD.
	  _Storage(const _Storage &) = delete;
	  _Storage &operator=(const _Storage &) = delete;
	
	  void *_M_ptr;
	  aligned_storage<sizeof(_M_ptr), alignof(void *)>::type _M_buffer;
	};
	void (*_M_manager)(_Op, const any*, _Arg*);
	_Storage _M_storage;
	// ...
};

std::any需要解决类型转换和小对象优化问题。
_Storage中,为了做小对象优化,我们将类型大小≤8字节的对象存储于_Storage._M_buffer中,如果对象大于8字节,则在堆上分配并将相应的堆指针存储于_Storage._M_ptr中。因为我们将对象以8字节大小分类,所以我们定义了两个模板:_Manager_internal_Manager_external
为了安全转换类型,std::any的做法是使用模板对每一个类型定义一个static函数:

enum _Op { _Op_access, _Op_get_type_info, _Op_clone, _Op_destroy, _Op_xfer };

union _Arg {
  void *_M_obj;
  const std::type_info *_M_typeinfo;
  any *_M_any;
};

// Manage in-place contained object.
template <typename _Tp> struct _Manager_internal {
  static void _S_manage(_Op __which, const any *__anyp, _Arg *__arg);
  // ...
}

// Manage external contained object.
template <typename _Tp> struct _Manager_external {
  static void _S_manage(_Op __which, const any *__anyp, _Arg *__arg);
  // ...
}

不同类型的static函数在内存地址空间上是独一无二的,因为可以使用static function address来区分类型。

std::any的构造函数

constexpr any() noexcept

/// Default constructor, creates an empty object.
constexpr any() noexcept : _M_manager(nullptr) { }

默认构造函数因为不存在对象,我们将函数指针设置未nullptr即可。

any( const any& other )

复制构造函数,has_value()其实就是判断std::any中是否存在对象,直接判断_M_manager是否为nullptr即可。然后本质上就是初始化std::any的两个成员,这里其实是利用static函数来做这个事情,即_M_manager指向的函数即可以用于判断类型是否相同,又可以做一些额外的操作,如这里的复制构造。

/// Copy constructor, copies the state of @p __other
any(const any &__other) {
  if (!__other.has_value())
    _M_manager = nullptr;
  else {
    _Arg __arg;
    __arg._M_any = this;
    __other._M_manager(_Op_clone, &__other, &__arg);
  }
}

any(any &&__other) noexcept

移动构造函数,对于_Manager_internal类型,_M_Storage._M_buffer要按位复制,对于_M_manager_external则只需要复制指针即可。

/**
 * @brief Move constructor, transfer the state from @p __other
 *
 * @post @c !__other.has_value() (this postcondition is a GNU extension)
 */
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);
  }
}

any(T&& value)

在需要给std::any初始化以及赋值时,我们仍然要知道数据的确切类型,另外这里其实是通用引用。这里要求_Tp是可复制构造和可原位构造。

/// Construct with a copy of @p __value as the contained object.
template <typename _Tp, typename _VTp = _Decay_if_not_any<_Tp>,
          typename _Mgr = _Manager<_VTp>,
          enable_if_t<is_copy_constructible<_VTp>::value &&
                          !__is_in_place_type<_VTp>::value,
                      bool> = true>
any(_Tp &&__value) : _M_manager(&_Mgr::_S_manage) {
  _Mgr::_S_create(_M_storage, std::forward<_Tp>(__value));
}

_Manager的作用是根据传入参数的类型决定使用_Manager_internal_Manager_external中的哪一个。判断的依据如下:
要求移动构造是noexcept,传入类型大小小于等于_Storage,对其要求亦如此。std::integral_constant的作用是给指定类型包装一个static变量,通过::value访问即可。

template <typename _Tp>
using _Manager = conditional_t<_Internal<_Tp>::value, _Manager_internal<_Tp>,
                               _Manager_external<_Tp>>;
/// Alias template for conditional
template <bool _Cond, typename _Iftrue, typename _Iffalse>
using conditional_t = typename conditional<_Cond, _Iftrue, _Iffalse>::type;

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>;

经过_Manager模板的包装,即可判断出参数类型应该选择哪一种存放数据的方式。无论是_Manager_internal<>还是_Manager_external<>模板,我们都会定义一个static函数,并在any(_Tp &&)初始化时将该函数指针赋值给_M_manager,然后初始化_M_storage即可。

// Manage in-place contained object.
template <typename _Tp> struct _Manager_internal {
  static void _S_manage(_Op __which, const any *__anyp, _Arg *__arg);

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

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

// Manage external contained object.
template <typename _Tp> struct _Manager_external {
  static void _S_manage(_Op __which, const any *__anyp, _Arg *__arg);

  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)...);
  }
};

template <typename _Tp>
void any::_Manager_internal<_Tp>::_S_manage(_Op __which, const any *__any,
                                            _Arg *__arg) {
  // The contained object is in _M_storage._M_buffer
  auto __ptr = reinterpret_cast<const _Tp *>(&__any->_M_storage._M_buffer);
  switch (__which) {
  case _Op_access:
    __arg->_M_obj = const_cast<_Tp *>(__ptr);
    break;
  case _Op_get_type_info:
#if __cpp_rtti
    __arg->_M_typeinfo = &typeid(_Tp);
#endif
    break;
  case _Op_clone:
    ::new (&__arg->_M_any->_M_storage._M_buffer) _Tp(*__ptr);
    __arg->_M_any->_M_manager = __any->_M_manager;
    break;
  case _Op_destroy:
    __ptr->~_Tp();
    break;
  case _Op_xfer:
    ::new (&__arg->_M_any->_M_storage._M_buffer)
        _Tp(std::move(*const_cast<_Tp *>(__ptr)));
    __ptr->~_Tp();
    __arg->_M_any->_M_manager = __any->_M_manager;
    const_cast<any *>(__any)->_M_manager = nullptr;
    break;
  }
}

template <typename _Tp>
void any::_Manager_external<_Tp>::_S_manage(_Op __which, const any *__any,
                                            _Arg *__arg) {
  // The contained object is *_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;
  case _Op_get_type_info:
#if __cpp_rtti
    __arg->_M_typeinfo = &typeid(_Tp);
#endif
    break;
  case _Op_clone:
    __arg->_M_any->_M_storage._M_ptr = new _Tp(*__ptr);
    __arg->_M_any->_M_manager = __any->_M_manager;
    break;
  case _Op_destroy:
    delete __ptr;
    break;
  case _Op_xfer:
    __arg->_M_any->_M_storage._M_ptr = __any->_M_storage._M_ptr;
    __arg->_M_any->_M_manager = __any->_M_manager;
    const_cast<any *>(__any)->_M_manager = nullptr;
    break;
  }
}

对于_Manager_internal类型的_S_create,因为不需要在堆上分配,直接使用placement new初始化即可,而_Manager_external则需要使用new申请空间并使用perfect forward参数初始化,并将指针存储在_Storage._M_ptr中。这里比较复杂的是_M_manage函数。

_S_manage

_Manager_internal_Manager_external存储数据的地址抽象为ptr,根据不同的Operation做出对应的操作。

  1. _Op_access:本质上就是返回std::any对象的_M_storage的引用,实现上会构造_Arg的栈对象。
  2. _Op_clone:将_Arg对象初始化为any指针,分配空间和使用复制构造函数初始化对象,然后将函数指针初始化为源对象的函数指针即可。
  3. _Op_destroy:对于internal,调用对象的析构函数即可(__ptr->~_Tp()),对于external,delete对应的指针即可。
  4. _Op_xfer:将any对象move到_Arg对象中,对于internal来说,其实就是对_Arg对象的any类型成员进行placement new,然后调用any对象的析构函数,初始化_Arg对象的_M_manager函数指针,最后将any*对象的_M_manager函数指针置为空,其实就是一个move语义,类似的,对于external,则需要进行堆上分配空间存储对象,剩下的动作和internal一样。

如何保证any_cast的正确性

假设我们希望从TypeA类型转型到TypeB,因为每一个类型都有一个static函数。在只有静态类型的情况下,我们判断&TypeA::_S_manage是否和&TypeB::_S_manage相等,如果不相等则类型不能转换,抛出异常,如果是动态类型,则需要rtti特性。

总结

std::any

在std::any中,一个any的类型其实依赖于初始化时传入的参数,即any(T&&)和emplace接口,编译期可以完全获取传入的类型,即可以完成后续的static function,以及各种接口的实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值