正常思路下,我们在实现std::any时需要保存data和type info用以any_cast成正确的类型,但存储空间占用较多。那么我们如何做到type map呢?在实现上可以使用lambdas来做,不过gcc的实现是通过any类中的manger模板类的static方法实现的。
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来区分类型。
/// Default constructor, creates an empty object.
constexpr any() noexcept : _M_manager(nullptr) { }
默认构造函数因为不存在对象,我们将函数指针设置未nullptr
即可。
复制构造函数,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);
}
}
移动构造函数,对于_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);
}
}
在需要给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
函数。
将_Manager_internal
和_Manager_external
存储数据的地址抽象为ptr
,根据不同的Operation做出对应的操作。
_Op_access
:本质上就是返回std::any
对象的_M_storage
的引用,实现上会构造_Arg
的栈对象。_Op_clone
:将_Arg
对象初始化为any指针,分配空间和使用复制构造函数初始化对象,然后将函数指针初始化为源对象的函数指针即可。_Op_destroy
:对于internal,调用对象的析构函数即可(__ptr->~_Tp()
),对于external,delete
对应的指针即可。_Op_xfer
:将any对象move
到_Arg对象中,对于internal来说,其实就是对_Arg对象的any类型成员进行placement new,然后调用any对象的析构函数,初始化_Arg对象的_M_manager函数指针,最后将any*对象的_M_manager函数指针置为空,其实就是一个move
语义,类似的,对于external,则需要进行堆上分配空间存储对象,剩下的动作和internal一样。假设我们希望从TypeA类型转型到TypeB,因为每一个类型都有一个static函数。在只有静态类型的情况下,我们判断&TypeA::_S_manage是否和&TypeB::_S_manage相等,如果不相等则类型不能转换,抛出异常,如果是动态类型,则需要rtti特性。
在std::any中,一个any的类型其实依赖于初始化时传入的参数,即any(T&&)和emplace接口,编译期可以完全获取传入的类型,即可以完成后续的static function,以及各种接口的实现。