-
Notifications
You must be signed in to change notification settings - Fork 16
/
smart_ptr.txt
239 lines (205 loc) · 16.2 KB
/
smart_ptr.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
SMART_PTR
HEADER ==> # <boost/scoped_ptr.hpp>
# <boost/scoped_array.hpp>
# <boost/shared_ptr.hpp>
# <boost/shared_array.hpp>
# <boost/weak_ptr.hpp>
# <boost/intrusive_ptr.hpp>
# <boost/make_shared.hpp>
# <boost/enable_shared_from_this.hpp>
# <boost/pointer_cast.hpp> : *_pointer_cast
COPY CONSTRUCTION ==> #auto_ptr a des problèmes avec la copy construction. La responsabilité d'un pointeur est passée à un
#auto_ptr copyconstruit à partir d'un autre. C'est souvent peu souhaitable : par exemple dans un
#container d'auto_ptr, faire AUTO_PTR = CONTAINER[0] fait que l'AUTO_PTR de CONTAINER devient un
#pointeur null.
#Solutions :
# - scoped_ptr : copy construction interdite
# - shared_ptr : copy construction incrémente un compteur. Destruction décrémente ce compteur : à 0,
# désallocation.
#Puisque scoped_ptr est non CopyConstructible, il ne peut pas être utilisé dans les standard containers
#par exemple.
AUTRES USAGES ==> #scoped_array et shared_array sont les pendants de scoped_ptr et shared_ptr mais pour un objet alloué
#avec new[] (array dynamique).
#weak_ptr sert à casser les cycles créés parfois par shared_ptr (cf fin)
#intrusive_ptr a mêmes buts que shared_ptr, mais plus léger, thread-safe, mais intrusif.
SEGFAULT ==> #Déréférencé un boost SMART_PTR ne crée pas de segfault, mais des assert( false ) runtime, donc même
#chose.
REQUIREMENTS FOR T ==> #Pour tout smart_ptr<T>, le destructor et l'operator delete de T doivent avoir la nothrow exception
#guarantee.
#T est toujours le type de l'underlying VAR, pas de l'ADR.
NOTHROW ==> #Les opérations des smart_ptr never throw, dont les fonctions *_shared, sauf :
# - constructors de tous, sauf CopyConstructor( shared_ptr ) et constructors de scoped_ptr
# - tous reset(), sauf ceux de weak_ptr
# - SHARED_PTR << et get_deleter()
PIMPL IDIOM ==> #Les smart pointeurs peuvent être utilisés comme des sortes de void* safes, permettant une type
#erasure (PIMPL idiom). Aussi appelé handle/body idiom.
CONTAINER VS SMART #On peut utiliser std::string et les containers pour des STR (char*) et des pointeurs de tableaux
POINTERS ==> #dynamiques, plutôt que les scoped_array ou shared_array :
# - containers : plus lourd, mais plus flexibles
# - boost::array : aussi léger, plus flexible, mais pas de taille dynamique
TRANSTYPAGE ==> #Tous les smart pointers :
# - On ne peut pas transtyper un SMART_PTR<T> vers SMART_PTR<U> si aucun lien d'héritage entre T et
# U, même si Convertible
#weak_ptr, shared_ptr et intrusive_ptr :
# - Si T est enfant de U, pas de problème : on peut donc utiliser le subtipe polymorphisme comme les
# pointeurs et les références.
#shared_ptr et intrusive_ptr :
# - Si T est parent de U, il faut faire *_pointer_cast<U>( SHARED_PTR<T> ), où * est reinterpret,
# static ou dynamic (comme dynamic_cast, etc.)
SERIALIZATION ==> #shared_ptr, scoped_ptr et weak_ptr sont Serializable avec boost::serialization
scoped_ptr<T> #Comme auto_ptr, sauf que :
# - swap() et ( SCOPED_PTR )
# - pas de release().
# - pas d'overloadable typecast
#Pour rappel :
...::element_type #Typedef depuis WVAR
SCOPED_PTR([ADR]) #Construit un SCOPED_PTR lié à la VAR pointé par ADR.
#N'alloue pas la mémoire d'ADR.
#Si celle-ci est déjà associée à un autre SCOPED_PTR, cela provoquera un segfault, car le deux
#SCOPED_PTR tenteront de reset() -> double free. Eviter donc cela.
#ADR est par défaut NULL.
*SCOPED_PTR #Renvoie une référence du pointeur sous-jacent déréférencé.
#Si contient NULL, provoque segfault.
SCOPED_PTR-> #Renvoie l'underlying pointeur.
SCOPED_PTR.get() #Permet aussi de savoir s'il s'agit d'un pointeur null.
SCOPED_PTR.reset([WVAL])#SCOPED_PTR
# - cesse d'être associé à WVAL (devient une sorte de pointeur NULL)
# - l'ancien WVAL est désalloué et détruit.
# - si un argument est précisé (de type WVAR), il devient associé à ce dernier
( SCOPED_PTR ) #Renvoie false si PTR est null.
SCOPED_PTR.swap #
( SCOPED_PTR )
swap( SCOPED_PTR1,
SCOPED_PTR2 ) #
scoped_array<T> #Comme scoped_ptr, mais pour un array alloué avec new[] :
# - utilise delete[] lors de la destruction
# - operator[] disponible
# - pas d'operator->
shared_ptr<T> #A un compteur :
# - construction : == 1, sauf si pointeur null
# - copy construction : ++
# - copie par référence : rien
# - destruction/reset() : -- (si == 0, désallocation)
#Mêmes membres que scoped_ptr, avec en plus :
SHARED_PTR(SHARD_PTR<T>,#Fait que l'ownership (le compteur) utilise SHARED_PTR<T>, mais la valeur enregistrée et déréférencée
T_ADR ) #est T_ADR. Utilisé pour casser des problèmes de cycles (cf fin)
SHARED_PTR( T_ADR, #Invoque FUNC( T_ADR ) pour désallouer T, au lieu de delete.
FUNC_ADR[, U_VAR] ) #Utile par exemple si T_ADR a été obtenu via une factory method.
#On peut aussi préciser l'Allocator U_VAR (par défaut std::Allocator)
SHARED_PTR( WEAK_PTR ) #Lance bad_weak_ptr si WEAK_PTR est un pointeur null.
SHARED_PTR( AUTO_PTR )
SHARED_PTR.reset([ARGS])#Mêmes arguments possibles que constructor
SHARED_PTR.use_count() #Renvoie le compteur sous forme de LONG_VAL. Utile surtout pour debugging, à éviter sinon.
SHARED_PTR.unique() #Renvoie true si use_count() == 1
SHARED_PTR == SHARD_PTR2
SHARED_PTR != SHARD_PTR2#Compare si contienne la même ADR (c'est-à-dire ADR avec adresses identiques)
SHARED_PTR < SHARED_PTR2#Compare selon des critères propres à l'implémentation. Intérêt : pouvoir trier un ensemble de
#SHARED_PTR dans un container, quelque soit l'ordre.
get_pointer( SHARED_PTR)#Renvoie SHARED_PTR.get()
get_deleter( SHARED_PTR)#Renvoie le FUNC_ADR utilisé pour désallouer si construit avec FONC_ADR, sinon renvoie 0.
BASIC_OSTRM << SHARD_PTR#Equivaut à << SHARED_PTR.get()
make_shared<T>( [ARGS] )#Renvoie boost::shared_ptr<T>( new T( ARGS ) ). VAL est par défaut rien.
#A utiliser pour initaliser un SHARED_PTR.
#Plus efficient que new.
#Plus sûr aussi :
# - boost::shared_ptr<T>( new T( ARGS ) ) utilisé comme valeur temporaire et comme argument parmi
# d'autres arguments peut être exception-unsafe, si un autre argument copié après new et avant
# la construction de shared_ptr lance une exception.
# make_shared fait au contraire les deux en une seule opération.
#Throw std::bad_alloc si problème.
allocate_shared<T,U> #
( U_VAL, [ARGS] ) #Comme make_shared, sauf que l'on utilise son propre Allocator U ( au lieu de std::allocator )
shared_array<T> #Equivalent de scoped_array pour shared_ptr.
#Même chose pour [], -> et delete[]
#Cependant ne possède pas get_pointer(), get_deleter(), <<, les constructors avec WEAK_PTR, AUTO_PTR
#et U_VAR (Allocator).
weak_ptr<T> #Utilisé comme un shared_ptr n'ayant pas d'ownership pour casser les cycles (cf fin)
#Il n'est pas responsable de son pointeur, ni ne peut directement l'utiliser (pas d'operator* ni de
#get())
#Il doit être instantié via un SHARED_PTR, et pour utiliser le pointeur sous-jacent doit instantier
#lui-même un SHARED_PTR (directement ou via lock()). Ainsi, on est sûr qu'au moyen un SHARED_PTR owns
#le pointeur lors de l'instantiation, mais aussi utilisation : si SHARED_PTR instantié avec un
#WEAK_PTR null, lance exception bad_weak_ptr.
#N'incrémente donc pas le compteur du SHARD_PTR associé.
#CopyConstructible.
...::element_type #
WEAK_PTR([SHARD_PTR<T>])#Si pas d'argument, WEAK_PTR null.
WEAK_PTR.use_count() #Renvoie le compteur du SHARED_PTR associé.
WEAK_PTR.expired() #Renvoie true si use_count() == 0
WEAK_PTR.lock() #Renvoie SHARED_PTR( *this ) (shared_ptr null si weak_ptr est null, et non exception)
WEAK_PTR.reset() #WEAK_PTR devient null (ne modifie pas le SHARED_PTR anciennement associé)
WEAK_PTR.swap(WEAK_PTR2)#
swap(WEAK_PTR,WEAK_PTR2)
WEAK_PTR < WEAK_PTR2 #Comme SHARED_PTR.
intrusive_ptr<T> #La manipulation de l'ownership est ici délégué à deux fonctions extérieures à T, mais
# - void intrusive_ptr_add_ref( T* ), devant faire que T incrémente le compteur de T_VAR de 1
# - void intrusive_ptr_release( T* ), devant faire que T décrémente le compteur de T_VAR de 1, et
# libère la mémoire allouée à T_ADR si compteur == 0
# - T doit donc avoir un compteur interne
#Le compteur est incrémenté lors de la construction et copy construction.
#Si dans la construction, un deuxième argument BOOL_VAL false (par défaut true) est donné, pas
#d'incrémentation.
#Buts :
# - plus léger que shared_ptr ou scoped_ptr
# - deux shared_ptr ou scoped_ptr construit à partir de la même VAL (et non copy construit) provoque
# un double delete. Causes possibles : inadvertance du développeur ; multithreading. En
# multithreading, il faut donc mettre des procédés de synchronization pour éviter cela, ce qui
# n'est pas nécessaire ici.
#Fonctions implémentées :
# - ...::element_type
# - INTRUSIVE_PTR( [T_ADR[, BOOL_VAL]] ) (incrémente compteur si BOOL_VAL == true et T_ADR != 0)
# - CopyConstruction (incrémente compteur)
# - reset( [T_ADR] )
# - *INTRUSIVE_PTR, INTRUSIVE_PTR->, INTRUSIVE_PTR.get(), get_pointer( INTRUSIVE_PTR )
# - ( INTRUSIVE_PTR )
# - INTRUSIVE_PTR == INTRUSIVE_PTR2 (et !=)
# - INTRUSIVE_PTR == T_VAR (et inversement, et !=)
# - INTRUSIVE_PTR < INTRUSIVE_PTR2
# - INTRUSIVE_PTR.swap( INTRUSIVE_PTR2 ), swap( INTRUSIVE_PTR, INTRUSIVE_PTR2 )
# - BASIC_OSTREAM << INTRUSIVE_PTR
ATTENTION ==> # - ne jamais initialisé avec un ADR temporaire (tel que &VAR), car il sera aussitôt désalloué, et
# la destruction du smart pointer fera un double delete.
# - pour initialiser avec "this", la classe doit dériver de enable_shared_from_this<classe>. Cela ne
# marche cependant pas dans un constructor : passer par une factory method
PROBLEMES DE CYCLES ==> #Exemple :
# - Une classe T contient un SHARED_PTR1<T>
# - On initialise un SHARED_PTR2<T> avec T_VAR.
# - On assigne SHARED_PTR2 à SHARED_PTR2->SHARED_PTR1
# - Donc SHARED_PTR2->SHARED_PTR1 et SHARED_PTR2 ont tous deux un compteur == 2
# - Quand SHARED_PTR2 disparaît, SHARED_PTR1 est détruit, mais compteur reste à 1 : T_VAR n'est pas
# désalloué et ne peut plus l'être (memory leak)
#Autre possibilité :
# - Une classe U contient un SHARED_PTR1<T>
# et une classe T contient un SHARED_PTR2<U>
# - et mêmes jeux de croisements
#Cas typique d'une telle boucle :
# - A est une classe contenant un ensemble de B.
# - Chaque B veut être inutilisable/détruit si le A le contenant est détruit : contient donc un
# shared_ptr<A>
# - A veut manipuler les B par référence, et voir s'il y a bien des B : contient donc un container de
# shared_ptr<B>
#Solution :
# - SHARED_PTR1<A> -> WEAK_PTR : A est responsable de la mise en inutilisation/destruction de tous
# les B lors de sa propre destruction (les B ne le pouvant plus par
# eux-mêmes)
# - SHARED_PTR2<B> -> WEAK_PTR : Le dernier B doit notifier à A qu'il n'y a plus de B lors de sa
# destruction (car A ne le peut plus par lui-même)
#Pour la récursion aussi mettre le SHARED_PTR inclus en WEAK_PTR.
#Conclusion : un shared_ptr vers un élement pouvant au final pointer (via 0 ou plusieurs shared_ptr)
#vers sa propre classe doit être un weak_ptr.
AUTRE PROBLEME ==> #Autre constructor( SHARED_PTR<T>, T_ADR ), permettant de pointer quelque chose de précis :
#la valeur est T_ADR, mais l'ownership est SHARED_PTR<T>.
#Soit :
# - une class T contenant un membre U.
# - un SHARED_PTR<T> et un SHARED_PTR<U>
# - on initialise SHARED_PTR<U> avec &SHARED_PTR<T>->U_VAR
# - SHARED_PTR<T> est détruit : SHARED_PTR<T>->U_VAR est désalloué, mais SHARED_PTR<U> demeure.
# - SHARED_PTR<U> est détruit : fait un nouveau delete sur U_VAR -> double delete
#Solution : initialisé SHARED_PTR<U> avec plutôt :
# - SHARED_PTR<U>( SHARED_PTR<T>, &SHARED_PTR<T>->U_VAR ) au lieu de ( &SHARED_PTR<T>->U_VAR )
#SHARED_PTR<U> est toujours initialisé avec U_VAR, mais SHARED_PTR<T> ne désalloue pas U_VAR lors de sa
#destruction. U_VAR n'est désalloué que lorsque les deux SHARED_PTR sont détruits.
#Conclusion :
# - si SHARED_PTR<MEMBRE> = &SHARED_PTR<PARENT>->MEMBRE, utiliser constructor spécial