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