Notes de lecture du code source STL (4) - Unique_ptr

avant-propos

Continuez à regarder le code source des éléments couramment utilisés dans stl et prévoyez de remplir les fosses des pointeurs intelligents. Cette fois, écrivez unique_prt en premier et écrivez shared_ptr la prochaine fois. Le code de unique_prt est relativement simple en général, et le seul endroit légèrement compliqué est l'extraction du destructeur personnalisé.

Version LIST

La version stl utilisée dans cet article est libc++ 13.0, qui appartient au projet LLVM.

Projet LLVM Github

Si vous rencontrez des définitions de macros inconnues, vous pouvez vous référer au document Macros de visibilité des symboles

Unique_ptr

Comme d'habitude, examinons les paramètres de modèle définis et certains typedefs.

template <class _Tp, class _Dp = default_delete<_Tp> >
class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
    
    
public:
  typedef _Tp element_type;
  typedef _Dp deleter_type;
  typedef _LIBCPP_NODEBUG_TYPE typename __pointer<_Tp, deleter_type>::type pointer;		//萃取是否有自定义的指针类型

  static_assert(!is_rvalue_reference<deleter_type>::value,
                "the specified deleter type cannot be an rvalue reference");
private:
  __compressed_pair<pointer, deleter_type> __ptr_;

Le premier paramètre de modèle _Tp est le type d'objet de stockage dont nous voulons définir le pointeur intelligent, et le deuxième paramètre de modèle _Dp est le type du destructeur. La valeur par défaut default_delete<_Tp> utilise un pointeur de suppression directe.

__compressed_pair est l'endroit où sont stockés le pointeur et le destructeur d'origine. Nous en reparlerons plus tard, examinons-le d'abord.

__point est une extraction de type. Le code source est le suivant. En termes simples, le pointeur sert à extraire le type de _Dp et à juger si _Dp définit son propre point. Par exemple, si le typedef point xxx est défini dans _Dp, ce xxx sera extrait. Sinon, ce sera _Tp*.

#define _LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(NAME, PROPERTY)                \
    template <class _Tp, class = void> struct NAME : false_type {
      
       };    \
    template <class _Tp>               struct NAME<_Tp, typename __void_t<typename _Tp:: PROPERTY >::type> : true_type {
      
       }

// __pointer
_LIBCPP_ALLOCATOR_TRAITS_HAS_XXX(__has_pointer, pointer);
template <class _Tp, class _Alloc,
          class _RawAlloc = typename remove_reference<_Alloc>::type,
          bool = __has_pointer<_RawAlloc>::value>
struct __pointer {
    
    
    using type _LIBCPP_NODEBUG_TYPE = typename _RawAlloc::pointer;
};
template <class _Tp, class _Alloc, class _RawAlloc>
struct __pointer<_Tp, _Alloc, _RawAlloc, false> {
    
    
    using type _LIBCPP_NODEBUG_TYPE = _Tp*;
};

Il n'est pas difficile de voir que __pointer est sélectivement spécialisé selon le quatrième paramètre de modèle, et le quatrième paramètre de modèle __has_pointer utilise sfinae pour juger s'il existe une chose telle que _Tp::point. sfinae est très courant et courant dans les applications C++.

Extraction de destructeurs personnalisés

private:
  __compressed_pair<pointer, deleter_type> __ptr_;

  struct __nat {
    
     int __for_bool_; };

  typedef _LIBCPP_NODEBUG_TYPE __unique_ptr_deleter_sfinae<_Dp> _DeleterSFINAE;

  template <bool _Dummy>
  using _LValRefType _LIBCPP_NODEBUG_TYPE =
      typename __dependent_type<_DeleterSFINAE, _Dummy>::__lval_ref_type;

  template <bool _Dummy>
  using _GoodRValRefType _LIBCPP_NODEBUG_TYPE =
      typename __dependent_type<_DeleterSFINAE, _Dummy>::__good_rval_ref_type;

  template <bool _Dummy>
  using _BadRValRefType _LIBCPP_NODEBUG_TYPE  =
      typename __dependent_type<_DeleterSFINAE, _Dummy>::__bad_rval_ref_type;

  template <bool _Dummy, class _Deleter = typename __dependent_type<
                             __identity<deleter_type>, _Dummy>::type>
  using _EnableIfDeleterDefaultConstructible _LIBCPP_NODEBUG_TYPE =
      typename enable_if<is_default_constructible<_Deleter>::value &&
                         !is_pointer<_Deleter>::value>::type;

  template <class _ArgType>
  using _EnableIfDeleterConstructible _LIBCPP_NODEBUG_TYPE  =
      typename enable_if<is_constructible<deleter_type, _ArgType>::value>::type;

  template <class _UPtr, class _Up>
  using _EnableIfMoveConvertible _LIBCPP_NODEBUG_TYPE  = typename enable_if<
      is_convertible<typename _UPtr::pointer, pointer>::value &&
      !is_array<_Up>::value
  >::type;

  template <class _UDel>
  using _EnableIfDeleterConvertible _LIBCPP_NODEBUG_TYPE  = typename enable_if<
      (is_reference<_Dp>::value && is_same<_Dp, _UDel>::value) ||
      (!is_reference<_Dp>::value && is_convertible<_UDel, _Dp>::value)
    >::type;

  template <class _UDel>
  using _EnableIfDeleterAssignable = typename enable_if<
      is_assignable<_Dp&, _UDel&&>::value
    >::type;

public:
  template <bool _Dummy = true,
            class = _EnableIfDeleterDefaultConstructible<_Dummy> >
  _LIBCPP_INLINE_VISIBILITY
  _LIBCPP_CONSTEXPR unique_ptr() _NOEXCEPT : __ptr_(pointer(), __default_init_tag()) {
    
    }

  template <bool _Dummy = true,
            class = _EnableIfDeleterDefaultConstructible<_Dummy> >
  _LIBCPP_INLINE_VISIBILITY
  _LIBCPP_CONSTEXPR unique_ptr(nullptr_t) _NOEXCEPT : __ptr_(pointer(), __default_init_tag()) {
    
    }

  template <bool _Dummy = true,
            class = _EnableIfDeleterDefaultConstructible<_Dummy> >
  _LIBCPP_INLINE_VISIBILITY
  explicit unique_ptr(pointer __p) _NOEXCEPT : __ptr_(__p, __default_init_tag()) {
    
    }

  template <bool _Dummy = true,
            class = _EnableIfDeleterConstructible<_LValRefType<_Dummy> > >
  _LIBCPP_INLINE_VISIBILITY
  unique_ptr(pointer __p, _LValRefType<_Dummy> __d) _NOEXCEPT
      : __ptr_(__p, __d) {
    
    }

  template <bool _Dummy = true,
            class = _EnableIfDeleterConstructible<_GoodRValRefType<_Dummy> > >
  _LIBCPP_INLINE_VISIBILITY
  unique_ptr(pointer __p, _GoodRValRefType<_Dummy> __d) _NOEXCEPT
      : __ptr_(__p, _VSTD::move(__d)) {
    
    
    static_assert(!is_reference<deleter_type>::value,
                  "rvalue deleter bound to reference");
  }

  template <bool _Dummy = true,
            class = _EnableIfDeleterConstructible<_BadRValRefType<_Dummy> > >
  _LIBCPP_INLINE_VISIBILITY
  unique_ptr(pointer __p, _BadRValRefType<_Dummy> __d) = delete;

  _LIBCPP_INLINE_VISIBILITY
  unique_ptr(unique_ptr&& __u) _NOEXCEPT
      : __ptr_(__u.release(), _VSTD::forward<deleter_type>(__u.get_deleter())) {
    
    
  }

  template <class _Up, class _Ep,
      class = _EnableIfMoveConvertible<unique_ptr<_Up, _Ep>, _Up>,
      class = _EnableIfDeleterConvertible<_Ep>
  >
  _LIBCPP_INLINE_VISIBILITY
  unique_ptr(unique_ptr<_Up, _Ep>&& __u) _NOEXCEPT
      : __ptr_(__u.release(), _VSTD::forward<_Ep>(__u.get_deleter())) {
    
    }

Cela a-t-il l'air compliqué à première vue ? En fait, je me sens aussi compliqué. La définition de l'extraction de destructeur en privé et du constructeur en public sera utilisée lors de la construction de unique_ptr

Nous commençons l'analyse à partir de la première étape _DeleterSFINAE

typedef _LIBCPP_NODEBUG_TYPE __unique_ptr_deleter_sfinae<_Dp> _DeleterSFINAE;
	
//具体定义
	template <class _Deleter>
struct __unique_ptr_deleter_sfinae {
    
    
  static_assert(!is_reference<_Deleter>::value, "incorrect specialization");
  typedef const _Deleter& __lval_ref_type;
  typedef _Deleter&& __good_rval_ref_type;
  typedef true_type __enable_rval_overload;
};

template <class _Deleter>
struct __unique_ptr_deleter_sfinae<_Deleter const&> {
    
    
  typedef const _Deleter& __lval_ref_type;
  typedef const _Deleter&& __bad_rval_ref_type;
  typedef false_type __enable_rval_overload;
};

template <class _Deleter>
struct __unique_ptr_deleter_sfinae<_Deleter&> {
    
    
  typedef _Deleter& __lval_ref_type;
  typedef _Deleter&& __bad_rval_ref_type;
  typedef false_type __enable_rval_overload;
};

_DeleterSFINAE est en fait un alias pour __unique_ptr_deleter_sfinae<_Dp>. __unique_ptr_deleter_sfinae<_Dp> sera spécialisé en fonction du type de paramètre de modèle _Dp. S'il s'agit d'une valeur, __good_rval_ref_type et __enable_rval_overload sont true_type. S'il s'agit d'une référence, __bad_rval_ref_type et __enable_rval_overload sont false_type.

Ici, __good_rval_ref_type et __bad_rval_ref_type sont résolus pour une extraction ultérieure.

La deuxième étape est de voir qui a utilisé ce _DeleterSFINAE, nous pouvons constater qu'il s'agit de _GoodRValRefType et _BadRValRefType

template <bool _Dummy>
  using _LValRefType _LIBCPP_NODEBUG_TYPE =
      typename __dependent_type<_DeleterSFINAE, _Dummy>::__lval_ref_type;

  template <bool _Dummy>
  using _GoodRValRefType _LIBCPP_NODEBUG_TYPE =
      typename __dependent_type<_DeleterSFINAE, _Dummy>::__good_rval_ref_type;

  template <bool _Dummy>
  using _BadRValRefType _LIBCPP_NODEBUG_TYPE  =
      typename __dependent_type<_DeleterSFINAE, _Dummy>::__bad_rval_ref_type;
//__dependent_type
template <class _Tp, bool>
struct _LIBCPP_TEMPLATE_VIS __dependent_type : public _Tp {
    
    };

Ici, _LValRefType, _GoodRValRefType et _BadRValRefType sont utilisés pour supprimer le type dans _DeleterSFINAE, et __dependent_type est utilisé pour différer la résolution ; le deuxième paramètre de modèle est utilisé

Je cite la réponse dans stackoverflow.La question sur stackoverflow est à quoi sert _Dummy.

répondre:

It is the dummy bool that makes the type dependent, this is the whole point of __dependent_type, otherwise you can just use the type itself.
Take this code as example:
	template <bool _Dummy>
	using _GoodRValRefType =
      typename __dependent_type<_DeleterSFINAE, _Dummy>::__good_rval_ref_type;
	Without the dummy to make it a dependent type, when the class template gets instantiated, _DeleterSFINAE::__good_rval_ref_type might cause a hard error, because not all of the _DeleterSFINAE has a __good_rval_ref_type member.
	The dependent type delays evaluation, so that you can use _GoodRValRefType in a SFINAE context later.

Le sens général est de le rendre dépendant du type et de retarder la décision.

Ensuite, il y a quelques définitions qui utilisent enable_if pour juger les caractéristiques, celles-ci sont relativement simples,

 using _EnableIfDeleterDefaultConstructible _LIBCPP_NODEBUG_TYPE =
      typename enable_if<is_default_constructible<_Deleter>::value &&
                         !is_pointer<_Deleter>::value>::type;

  template <class _ArgType>
  using _EnableIfDeleterConstructible _LIBCPP_NODEBUG_TYPE  =
      typename enable_if<is_constructible<deleter_type, _ArgType>::value>::type;

  template <class _UPtr, class _Up>
  using _EnableIfMoveConvertible _LIBCPP_NODEBUG_TYPE  = typename enable_if<
      is_convertible<typename _UPtr::pointer, pointer>::value &&
      !is_array<_Up>::value
  >::type;

  template <class _UDel>
  using _EnableIfDeleterConvertible _LIBCPP_NODEBUG_TYPE  = typename enable_if<
      (is_reference<_Dp>::value && is_same<_Dp, _UDel>::value) ||
      (!is_reference<_Dp>::value && is_convertible<_UDel, _Dp>::value)
    >::type;

  template <class _UDel>
  using _EnableIfDeleterAssignable = typename enable_if<
      is_assignable<_Dp&, _UDel&&>::value
    >::type;

La dernière étape consiste à combiner différents constructeurs pour correspondre à l'extraction ci-dessus. Je pense que c'est correct quand je le regarde, mais une fois que j'ai trié ces choses, c'est encore un peu écrasant. Quant à ce qui est si compliqué, je pense personnellement que pour retarder la liaison de _DeleterSFINAE Find, c'est-à-dire la réponse stackoverflow précédente.

__compressed_pair<pointer, deleter_type> _ ptr ;

Le résultat du stockage du pointeur d'origine et du destructeur personnalisé est simplement une paire optimisée, qui est une optimisation de classe de base vide. Lorsqu'il n'y a pas de destructeur personnalisé, aucun espace n'est perdu.

おすすめ

転載: blog.csdn.net/ninesnow_c/article/details/123063863