• STL源码阅读小记(六)——Any


    前言

    之前一直有听说过,不过倒是没怎么使用过,用来代替void进行类型转换,比void转换更加安全。

    STL版本

    本文所使用的stl版本为libc++ 13.0,属于LLVM项目。

    LLVM项目Github

    如果遇到不熟悉的宏定义可以参考文档Symbol Visibility Macros

    Any

    any是在c++17才支持的一种类型,内部可以存储任意类型,原理是利用了类型擦除,在c++常见的类型擦除有两种办法,一种是通过虚函数,如function就是利用虚函数进行类型擦除,还有一种就算利用void类型,any就是使用这种方法。

    我们看一下any的定义和内部成员

    class _LIBCPP_TEMPLATE_VIS any
    {
        typedef __any_imp::_Action _Action;
        using _HandleFuncPtr =  void* (*)(_Action, any const *, any *, const type_info *,
          const void* __fallback_info);
    
        union _Storage {
            constexpr _Storage() : __ptr(nullptr) {}
            void *  __ptr;
            __any_imp::_Buffer __buf;
        };
        template 
        friend struct __any_imp::_SmallHandler;
        template 
        friend struct __any_imp::_LargeHandler;
    
        _HandleFuncPtr __h = nullptr;
        _Storage __s;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    可以发现any并非是一个模板类,内部有个函数指针和存储地方,在内部对象小于3sizeof(void)时会存储在自身上,大于的话就会在堆上申请空间。any的操作基本不是由自身完成的而是通过__any_imp中的_SmallHandler和_LargeHandler操作的。

    构造函数

    由于构造函数有好几个,并且每个模板都好长好长,就挑一个典型的吧。

    //声明
    template ,
        class = enable_if_t<
            is_constructible<_Tp, _Args...>::value &&
            is_copy_constructible<_Tp>::value
        >
      >
      _LIBCPP_INLINE_VISIBILITY
      explicit any(in_place_type_t<_ValueType>, _Args&&... __args);
    
    //定义
    template 
    any::any(in_place_type_t<_ValueType>, _Args&&... __args) {
      __any_imp::_Handler<_Tp>::__create(*this, _VSTD::forward<_Args>(__args)...);
    }
    
    template 
      using _Handler = conditional_t<
        _IsSmallObject<_Tp>::value, _SmallHandler<_Tp>, _LargeHandler<_Tp>>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    可以发现声明中利用模板检测存放的类型是否有对于的构造函数和拷贝构造函数,在定义中委托给_Handler处理,而_Handler其实是一个编译期的?:三元运算,如果是小对象则_Handler为_SmallHandler,否则为_LargeHandler。

    像拷贝构造函数移动构造函数基本都是如此,后续我们再看_SmallHandler和_LargeHandler究竟做了什么。

    __any_imp

    _SmallHandler

    _SmallHandler内部是一些静态函数,比如create,copy,move等等。

    我们先看看create函数

    template 
        _LIBCPP_INLINE_VISIBILITY
        static _Tp& __create(any & __dest, _Args&&... __args) {
            typedef allocator<_Tp> _Alloc;
            typedef allocator_traits<_Alloc> _ATraits;
            _Alloc __a;
            _Tp * __ret = static_cast<_Tp*>(static_cast(&__dest.__s.__buf));
            _ATraits::construct(__a, __ret, _VSTD::forward<_Args>(__args)...);
            __dest.__h = &_SmallHandler::__handle;
            return *__ret;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以发现any的构造实际上是在这里进行的,利用allocator在any的__s上分配空间并且构造,然后将_SmallHandler::__handle函数指针复制到any的__h上。

    在_SmallHandler中还有一个__handle静态函数会被注册到any的__h上,这个函数主要作用就是根据参数传入调用不同的函数。

    static void* __handle(_Action __act, any const * __this, any * __other,
                               type_info const * __info, const void* __fallback_info)
         {
            switch (__act)
            {
            case _Action::_Destroy:
              __destroy(const_cast(*__this));
              return nullptr;
            case _Action::_Copy:
                __copy(*__this, *__other);
                return nullptr;
            case _Action::_Move:
              __move(const_cast(*__this), *__other);
              return nullptr;
            case _Action::_Get:
                return __get(const_cast(*__this), __info, __fallback_info);
            case _Action::_TypeInfo:
              return __type_info();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    _LargeHandler

    _LargeHandler结构基本和_SmallHandler一样,只不过是分配空间的位置不同,就不细写了。

    any_cast函数

    最后就是将any转为原本类型的any_cast函数,any_cast本质上还是用来static_cast,只不过会对any中的对象进行一次判空检查,如果为空则会抛出异常。

    template 
    inline _LIBCPP_INLINE_VISIBILITY
    _LIBCPP_AVAILABILITY_THROW_BAD_ANY_CAST
    _ValueType any_cast(any const & __v)
    {
        using _RawValueType = __uncvref_t<_ValueType>;
        static_assert(is_constructible<_ValueType, _RawValueType const &>::value,
                      "ValueType is required to be a const lvalue reference "
                      "or a CopyConstructible type");
        auto __tmp = _VSTD::any_cast>(&__v);
        if (__tmp == nullptr)
            __throw_bad_any_cast();
        return static_cast<_ValueType>(*__tmp);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    “集世界精华·展中国风采”2024北京智能科技展会(世亚智博会)
    Outlook邮件视图设置怎么修复
    shiro
    【xv6操作系统】Lab systems calls
    【Dotnet 工具箱】WPF UI - 现代化设计的开源 WPF 框架
    【uniapp基础篇】上传图片
    【JAVA】-- 简易超市管理系统(二)(实现思路+每步代码)
    Web基础与HTTP协议
    数据库管理系统:Redis配置与使用
    Can‘t connect to local MySQL server through socket ‘/tmp/mysql.sock‘ (2)
  • 原文地址:https://blog.csdn.net/ninesnow_c/article/details/126499063