-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy path07-moreCpp.Rmd
368 lines (274 loc) · 9.94 KB
/
07-moreCpp.Rmd
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
```{R echo = FALSE, warnings = FALSE}
source("include.cpp.r")
includeCppPath <- "../introRcppMore/src/"
```
# Un peu plus de C++
Install the package with
```{R eval = FALSE, prompt = TRUE}
devtools::install_github("introRcpp/introRcppMore")
```
Load it with
```{R}
library(introRcppMore)
```
## Pointeurs
Les pointeurs sont un héritage de C. Il s'agit de \og pointer\fg\ vers
l'adresse d'un objet ou une variable. Un pointeur vers un `int` est un `int *`.
Si `p` est un pointeur, `*p` est l'objet à l'adresse pointée par `p`.
Pour obtenir le pointeur vers `x`, on utilise `&x`.
```{R echo = FALSE, results = "asis"}
include.cpp('pointers.cpp')
```
```{r prompt = TRUE, comment = NA}
pointers()
```
Nous avions déjà rencontré des pointeurs sans le savoir : les tableaux
sont des pointeurs ! Plus précisément, si on a déclaré `int a[4]`,
`a` pointe vers le premier élément du tableau, `a+1` vers le second, etc.
```{R echo = FALSE, results = "asis"}
include.cpp('arrays.cpp')
```
```{r prompt = TRUE, comment = NA}
arrays()
```
Notez la différence entre `a` et `a+2` : la taille des `int` est prise en compte
dans le calcul.
En passant à une fonction un pointeur vers une variable,
d'une part on permet la modification de cette variable, d'autre part on
évite la copie de cette variable (intéressant quand on passe des
objets de grande taille).
```{R echo = FALSE, results = "asis"}
include.cpp('swap.cpp')
```
Attention, la fonction `swap` ne peut pas être exportée vers R : les objets
de R ne peuvent être transformés en `int *`, en pointeurs vers des entiers.
Elle est destinée à n'être utilisée que dans notre code C++.
Le mot clef `inline` ci-dessus indique au compilateur qu'on désire que la
fonction soit insérée dans le code aux endroit où elle est utilisée. Cela
économise un appel de fonction...
```{r prompt = TRUE, comment = NA}
demonstrateSwap(12, 14)
demonstrateSwap(14, 12)
```
## Références
Le mécanisme derrière les références est similaire à celui des pointeurs ; la syntaxe est
plus simple et permet d'éviter de
promener des étoiles dans tout le code. Une référence à un entier est de type `int &`,
et peut être manipulée directement comme un `int`. On l'initialise à l'aide d'une
autre variable de type `int`.
```{R echo = FALSE, results = "asis"}
include.cpp('references.cpp')
```
```{r prompt = TRUE, comment = NA}
references()
```
C'est surtout utile pour spécifier les arguments d'une fonction.
Voici ce que devient notre fonction `swap`:
```{R echo = FALSE, results = "asis"}
include.cpp('swap2.cpp')
```
Cette fonction `swap2` ne peut pas plus être exportée que la
fonction `swap` définie plus haut.
```{r prompt = TRUE, comment = NA}
demonstrateSwap2(12, 14)
demonstrateSwap2(14, 12)
```
### Autres exemples
Un appel `divideByTwo(x)` est équivalent à `x /= 2`. L'utilisation de
cette fonction ne
simplifie pas vraiment le code, mais ce mécanisme pourrait être
utile pour des opérations plus complexes:
```{R echo = FALSE, results = "asis"}
include.cpp('divideByTwo.cpp')
```
```{r prompt = TRUE, comment = NA}
intLog2(17)
```
Une autre utilité est de renvoyer plusieurs valeurs. Bien sûr quand
on utilise `Rcpp` on peut manipuler des vecteurs ou des listes, mais
utiliser des variables passées par référence pour récupérer les résultats
d'un calcul est souvent très commode.
```{R echo = FALSE, results = "asis"}
include.cpp('quadratic.cpp')
```
```{r prompt = TRUE, comment = NA}
demoQuadratic(1,-5,6)
demoQuadratic(1,0,1)
```
## Les objets de Rcpp sont passés par référence
La fonction suivante le démontre : les vecteurs de Rcpp sont passés par référence
(ou sont des pointeurs, comme vous préférez).
Ceci permet un grand gain de temps, en évitant la copie des données,
mais a aussi des effets potentiellement indésirables.
```{R echo = FALSE, results = "asis"}
include.cpp('twice.cpp')
```
```{r prompt = TRUE, comment = NA}
x <- seq(0,1,0.1);
x
twice(x)
x # oups
```
De plus, quand on copie un objet en R, comme ci-dessous avec `y <- x`, la recopie des données n'a pas lieu dans un premier
temps. Au début, `y` pointe vers le même objet que `x` ; la recopie n'aura lieu que si on modifie `y`. Quand on utilise
`Rcpp` ce mécanisme n`est plus actif:
```{r prompt = TRUE, comment = NA}
x <- seq(0,1,0.1);
y <- x
twice(y)
x
y
```
La prudence est de mise! Notez qu'on a aussi le comportement suivant:
```{r prompt = TRUE, comment = NA}
x <- 1:5;
x
twice(x)
x
```
Utiliser `typeof(x)` pour résoudre cette énigme.
À supposer que ce comportement soit indésirable, comment y remédier ?
```{R echo = FALSE, results = "asis"}
include.cpp('twice2.cpp')
```
```{r prompt = TRUE, comment = NA}
x <- seq(0,1,0.1); invisible(twice2(x)); x
x <- seq(0,1,0.1); invisible(twice3(x)); x
```
On peut également modifier « en place » les éléments d'une liste:
```{R echo = FALSE, results = "asis"}
include.cpp('incrementAlpha.cpp')
```
Exemple:
```{r prompt = TRUE, comment = NA}
x <- list( alpha = c(0.1,7), beta = 1:4)
x
incrementAlpha(x)
x
```
## Surcharge
En C++ deux fonctions peuvent avoir le même nom, à condition que le type des
arguments soit différent,
ce qui permet au compilateur de les différentier. À noter : le type retourné
par la fonction n'est pas pris en compte dans ce mécanisme.
Ceci s'appelle la *surcharge* des fonctions. On a également une surcharge
des opérateurs (comme `+`, `<<`, etc).
**Attention !** On ne peut pas exporter des fonctions surchargées avec Rcpp.
Il faut donc maintenir ce mécanisme dans la partie strictement C++ de votre
code.
Voici un exemple de fonction surchargée.
```{R echo = FALSE, results = "asis"}
include.cpp('d2.cpp')
```
```{r prompt = TRUE, comment = NA}
exampleD2()
```
On peut également avoir des fonctions qui ont le même nom, et un nombre différent d'arguments.
## Templates
Les templates facilitent la réutilisation de code avec des types différents.
Prendre le temps d'utiliser des méthodes propres pour éviter les copier-coller
dans le code est de toute première importance quand il s'agit de \og maintenir\fg\ le
code.
### Fonctions polymorphes
L'exemple ci-dessous définit un template `d2` ; lors de la compilation
de la fonctions `addition_int` et `addition_double` ce template sera instancié
avec `TYPE = int` puis avec `TYPE = double`.
```{R echo = FALSE, results = "asis"}
include.cpp('d2.h')
include.cpp('exampleTemplate.cpp')
```
Exemple:
```{r prompt = TRUE, comment = NA}
exampleTemplate()
```
Si le compilateur peinait à trouver par quoi il faut remplacer `TYPE`, il serait
possible de préciser en tapant par exemple `d2<float>`. Ce template peut fonctionner
dès que les opérateurs `+` et `*` sont définis.
```{R echo = FALSE, results = "asis"}
include.cpp('exempleTemplate2.cpp')
```
```{r prompt = TRUE, comment = NA}
exempleTemplate2( c(1,2), c(3,1) )
```
On peut donner un autre exemple en faisant un template pour la fonction `swap`
que nous avions écrite il y a quelques chapitres.
```{R echo = FALSE, results = "asis"}
include.cpp('demonstrateSwap3.cpp')
```
```{r prompt = TRUE, comment = NA}
demonstrateSwap3(12, 14)
demonstrateSwap3(14, 12)
```
**Note :** n'utilisez pas ce template dans votre code,
C++ a déjà un template `std::swap`, qui est mieux fait que celui-ci ! (Il
évite la copie). Voir https://en.cppreference.com/w/cpp/algorithm/swap
### Laisser le compilateur s'occuper des types
Une des utilités des templates est de permettre de ne pas trop
se soucier de types « compliqués » comme ceux des fonctions. Ci-dessous
on définit une fonction `numDerivation` qui donne une approximation de
la dérivée de son premier argument.
```{R echo = FALSE, results = "asis"}
include.cpp('derivSquare.cpp')
```
```{r prompt = TRUE, comment = NA}
derivSquare(3)
```
On voit que `numDerivation` prend deux arguments, une fonction `f` et un point `a`,
et donne une approximation de $f'(a)$ en calculant
$$ {f(a+\varepsilon) - f(a-\varepsilon) \over 2\varepsilon} . $$
Quel est le type d'une fonction ?! Nous laissons
au compilateur le soin de remplacer `FTYPE` par la bonne valeur.
Dans notre exemple, lors d'instanciation du template, `TYPE` est remplacé par `double`
et `FTYPE` par `double (*)(double)` (pointeur vers une fonction
qui renvoie un `double` et dont l'argument est un `double`). Un code équivalent serait
```{R echo = FALSE, results = "asis"}
include.cpp('derivSquare.alt')
```
## C++11
Le standard C++11 est maintenant accepté dans les packages R. Pour permettre
la compilation avec les extensions offertes par C++11,
il faut inclure dans `src/` un fichier `Makevars` contenant
```
PKG_CXXFLAGS = -std=c++11
```
### Les boucles sur un vecteur et `auto`
Parmi les extensions offertes, les plus séduisantes sont sans doute : le mot-clef `auto`,
qui laisse le compilateur deviner le type, quand le contexte le permet, de façon similaire
à ce qui est fait dans les templates, et les boucles
`for` qui parcourent un vecteur... comme en R.
```{R echo = FALSE, results = "asis"}
include.cpp('countZeroes.cpp')
```
```{r prompt = TRUE, comment = NA}
set.seed(1); a <- sample(0:99, 1e6, TRUE)
countZeroes(a);
```
Si on veut pouvoir modifier la valeur des éléments du vecteur, il suffit
d'utiliser une référence :
```{R echo = FALSE, results = "asis"}
include.cpp('addOne.cpp')
```
```{r prompt = TRUE, comment = NA}
a <- 1:10
addOne(a)
a
```
**NOTE** En utilisant une référence, on évite la copie. Même si on ne désire pas modifier les éléments
du vecteur, cela peut modifier énormément les performances de la boucle en question !
**TODO ajouter exemple !!!**
### Nombres spéciaux
C++11 propose aussi des fonctions pour tester si des nombres flottants sont
finis ou non, sont `NaN`. Il est aussi plus facile de renvoyer un `NaN` ou une
valeur infinie (c'était possible avec `std::numeric_limits`).
```{R echo = FALSE, results = "asis"}
include.cpp('tests.cpp')
```
```{R echo = FALSE, results = "asis"}
include.cpp('specials.cpp')
```
```{r prompt = TRUE, comment = NA}
tests( NA )
tests( -Inf )
tests( pi )
specials();
```