STL source code reading notes (6) - Any

foreword

I have heard of it before, but I haven't used it much. It is used to replace void for type conversion, which is safer than void conversion.

STL version

The stl version used in this article is libc++ 13.0, which belongs to the LLVM project.

LLVM project Github

If you encounter unfamiliar macro definitions, you can refer to the document Symbol Visibility Macros

Any

any is a type supported only in c++17. Any type can be stored inside. The principle is to use type erasure. There are two common types of type erasure in c++. One is through virtual functions, such as function Use virtual functions for type erasure, and there is another method that uses void types, any uses this method.

Let's take a look at the definition and internal members of 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 <class>
    friend struct __any_imp::_SmallHandler;
    template <class>
    friend struct __any_imp::_LargeHandler;

    _HandleFuncPtr __h = nullptr;
    _Storage __s;
}

It can be found that any is not a template class. There is a function pointer and storage place inside. When the internal object is smaller than 3 sizeof(void ), it will be stored on itself, and if it is larger, it will apply for space on the heap. The operation of any is basically not done by itself but through _SmallHandler and _LargeHandler in __any_imp.

Constructor

Since there are several constructors, and each template is very long, let's pick a typical one.

//声明
template <class _ValueType, class ..._Args,
    class _Tp = decay_t<_ValueType>,
    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 <class _ValueType, class ..._Args, class _Tp, class>
any::any(in_place_type_t<_ValueType>, _Args&&... __args) {
  __any_imp::_Handler<_Tp>::__create(*this, _VSTD::forward<_Args>(__args)...);
}

template <class _Tp>
  using _Handler = conditional_t<
    _IsSmallObject<_Tp>::value, _SmallHandler<_Tp>, _LargeHandler<_Tp>>;

It can be found that the template is used to detect whether the stored type has a corresponding constructor and copy constructor, and the definition is entrusted to _Handler for processing, and _Handler is actually a compile-time one? : Ternary operation, if it is a small object, the _Handler is _SmallHandler, otherwise it is _LargeHandler.

This is basically the case for copy constructors and move constructors. Let's see what _SmallHandler and _LargeHandler do later.

__any_imp

_SmallHandler

Inside _SmallHandler are some static functions, such as create, copy, move and so on.

Let's look at the create function first

template <class ..._Args>
    _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<void*>(&__dest.__s.__buf));
        _ATraits::construct(__a, __ret, _VSTD::forward<_Args>(__args)...);
        __dest.__h = &_SmallHandler::__handle;
        return *__ret;
    }

It can be found that the construction of any is actually carried out here, using the allocator to allocate space on the __s of any and constructing it, and then copy the _SmallHandler::__handle function pointer to the __h of any.

In _SmallHandler, there is also a __handle static function that will be registered on any __h. The main function of this function is to call different functions according to the parameters.

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<any &>(*__this));
          return nullptr;
        case _Action::_Copy:
            __copy(*__this, *__other);
            return nullptr;
        case _Action::_Move:
          __move(const_cast<any &>(*__this), *__other);
          return nullptr;
        case _Action::_Get:
            return __get(const_cast<any &>(*__this), __info, __fallback_info);
        case _Action::_TypeInfo:
          return __type_info();
        }
    }

_LargeHandler

The structure of _LargeHandler is basically the same as that of _SmallHandler, except that the location of the allocated space is different, so I won’t describe it in detail.

any_cast function

The last is to convert any to the original type of any_cast function. any_cast is essentially used for static_cast, but it will perform a null check on the object in any, and an exception will be thrown if it is empty.

template <class _ValueType>
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<add_const_t<_RawValueType>>(&__v);
    if (__tmp == nullptr)
        __throw_bad_any_cast();
    return static_cast<_ValueType>(*__tmp);
}

Guess you like

Origin blog.csdn.net/ninesnow_c/article/details/126499063