Exemple :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Dans un fichier X : void foo(const Type& paramIn, Type& paramInOut); // Dans un autre fichier Y : void uneFonction() { Type obj1, obj2, *ptr1, *ptr2; // ... foo(obj1, obj2); if(ptr1 != nullptr && ptr2 != nullptr) { foo(*ptr1, *ptr2); } } |
Une première solution serait d'adopter la convention suivante :
- Les paramètres IN/OUT sont toujours passés par pointeur vers type non constant.
- Les paramètres IN sont passés par défaut par référence constante. Ils sont passés par pointeur vers type constant si et seulement si ce pointeur peut être nul.
Le code devient alors :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Dans un fichier X : void bar(const Type& paramIn, Type* paramInOut); // précondition : paramInOut != nullptr // Dans un autre fichier Y : void uneFonction() { Type obj1, obj2, *ptr1, *ptr2; // ... bar(obj1, &obj2); if(ptr1 != nullptr && ptr2 != nullptr) { bar(*ptr1, ptr2); } } |
Mais il y a un inconvénient : Dans la version avec foo(*ptr1, *ptr2), grâce à l'étoile, l'utilisateur sait que ptr2 doit être non nul. Par contre, dans la version avec bar(*ptr1, ptr2), l'utilisateur risque d'oublier le test ptr2 != nullptr.
Pour pallier un peu ce problème, on peut utiliser gsl::not_null (vanté dans cet article), mais ce n'est pas la panacée.
Une autre solution serait que chaque paramètre IN/OUT soit signalé à chaque fois de manière explicite à l'initiative de l'appelant de la fonction.
Exemple 1 :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | // Dans le fichier Y : void uneFonction() { Type obj1, obj2, *ptr1, *ptr2; // ... foo(obj1, obj2); // peut modifier obj2 ! if(ptr1 != nullptr && ptr2 != nullptr) { foo(*ptr1, *ptr2); // peut modifier *ptr2 ! } } |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // Dans le fichier Y : void uneFonction() { Type obj1_mutable; Type obj2_mutable; Type* ptr1_canModify; Type* ptr2_canModify; const Type& obj1 = obj1_mutable; const Type& obj2 = obj2_mutable; const Type* const & ptr1 = ptr1_canModify; const Type* const & ptr2 = ptr2_canModify; // ... foo(obj1, obj2_mutable); if(ptr1 != nullptr && ptr2 != nullptr) { foo(*ptr1, *ptr2_canModify); } } |
Personnellement, actuellement, je passe les paramètres IN/OUT par référence non constante.
Si je vois un paramètre IN/OUT qui porte à confusion, j'ajoute un commentaire du style "peut modifier tel paramètre" lors de chaque appel à la fonction.
Et vous ?