Comment bien indiquer les arguments en entrée et en sortie d'une fonction ?
Le langage ne dispose pas d'une syntaxe spécifique
Le 2016-07-14 22:46:32, par Pyramidev, Expert éminent
Au niveau de la syntaxe d'appel des fonctions, le C++ ne permet pas à l'utilisateur de différencier du premier coup d'œil les paramètres IN des paramètres IN/OUT.
Exemple :
Sans lire le fichier X, le lecteur du fichier Y ne peut pas deviner que le 1er paramètre de foo est IN tandis que le 2e est IN/OUT.
Une première solution serait d'adopter la convention suivante :
Le code devient alors :
Alors, quand l'utilisateur observe ce code dans le fichier Y, il sait que, selon cette convention, bar ne modifie ni obj1, ni *ptr1. Il n'a pas besoin d'aller chercher cette information dans le fichier X.
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 :
Exemple 2 :
L'inconvénient est que l'appelant de la fonction a de fortes chances de ne pas avoir ce genre d'initiative.
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 ?
Exemple :
Code : |
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 : |
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 : |
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 : |
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 ?
-
jo_link_noirMembre expertJ'opterais pour un type spécifique avec un constructeur explicite pour que l'appelant n'ait l'alternative de l'ignorer.
Plus une fonction de construction pour ne pas se taper la déduction du type: foo(obj1, inout_param(obj2));.le 14/07/2016 à 23:48 -
JolyLoicRédacteur/ModérateurCertains coding styles (ceux de google par exemple) imposent ta solution à base de pointeur pour in/out, référence pour in.
J'avoue avoir du mal à être convaincu par cet argument, pour les raisons suivantes :
- Le cas le plus courant de paramètre in/out est géré par la syntaxe objet : f(a /*in/out*/, b); => a.f(b); (petit aparté amusant : parmi les personnes que j'ai pu rencontrer qui disent qu'il faudrait distinguer au niveau de l'appel les passages par références constante ou non, je n'ai jamais rencontré personne voulant différencier au niveau de l'appel une fonction membre const d'une non const, alors que c'est exactement le même problème).
- En dehors de ce cas, les paramètres in/out sont généralement assez rares, et les paramètres n'étant que out sont généralement avec profit transmis par la valeur de retour de la fonction.
- Les exemples comme tu as montré avec des fonction foo et bar peuvent sembler convaincants, mais généralement, dans du vrai code, le nom de la fonction et éventuellement de ses arguments permet de lever le doute sans aucun effort. Je ne suis encore jamais tombé sur une exemple où je ressentre une nécessité d'expliciter la modification du paramètre (sauf dans du vieux code où une référence non constante est utilisée pour un paramètre out)...
Si je prends un exemple, le premier qui me vient (il n'y a pas tellement de passages par référence non constante dans la lib standard...) :
Code : 1
2
3
4std::list<int> l1, l2; /*...*/ auto it = l1.find(/* ...*/); l1.splice(it, l2);
le 15/07/2016 à 1:31 -
MédinocExpert éminent sénior
- L'objectif, c'est de voir du premier coup d'œil, donc "passer la souris dessus" ne remplit pas cet objectif.
- Bonne chance pour les codes sur dvp, car à ma connaissance aucun navigateur ne fait ça. Ensuite, si tu copie-colles le code, il te faut un éditeur plus lourd que Notepad++ (qui ne fait pas de reconnaissance) et plus léger que Visual Studio, qui exige un projet avant de le faire.
le 21/07/2016 à 7:25 -
EhonnMembre chevronnéPlutôt non, car :
- comme la solution des macros, le compilateur ne peut pas faire de vérification
- tu peux utiliser une même variable pour différents paramètres et je ne pense pas que les codes ci dessous soient une bonne idée
Code : 1
2
3T ioT; std::cin << ioT; // Devrait être oT; std::cout >> ioT; // Devrait être iT;
Code : 1
2
3
4
5T t; T const & iT = t; T & oT = t; std::cin << oT; std::cout >> iT;
le 21/07/2016 à 19:22 -
PyramidevExpert éminentBonne idée !
Ça donnerait quelque chose du genre :
inout.h :
Code : 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#include <type_traits> template<typename T> class inout { static_assert(std::is_same< T, typename std::remove_const<T>::type >::value, "Erreur : Le type doit ne pas être constant."); private: T& m_ref; public: explicit inout(T& x) : m_ref(x) {} T& get() {return m_ref;} }; template<typename T> inout<T> make_inout(T& x) { // sera inutile en C++17 return inout<T>(x); } template<typename T> class opt_inout { static_assert(std::is_same< T, typename std::remove_const<T>::type >::value, "Erreur : Le type doit ne pas être constant."); private: T* const m_ptr; public: explicit opt_inout(T* x) : m_ptr(x) {} T* get() {return m_ptr;} }; template<typename T> inout<T> make_opt_inout(T* x) { // sera inutile en C++17 return opt_inout<T>(x); }
Code : void foo(const Type& paramIn, inout<Type> paramInOut);
Code : 1
2
3
4
5
6
7
8
9
10
11void uneFonction() { Type obj1, obj2, *ptr1, *ptr2; // ... foo(obj1, make_inout(obj2)); if(ptr1 != nullptr && ptr2 != nullptr) { foo(*ptr1, make_inout(*ptr2)); } }
le 15/07/2016 à 1:08 -
foetusExpert éminent séniorEt pourquoi pas "un truc à l'ancienne" couplé avec tes recommandations
Le truc à l'ancienne:
Code : 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/** * This is a definition which has sole purpose of helping readability. * It indicates that formal parameter is an input parameter. */ #ifndef IN #define IN #endif /** * This is a definition which has sole purpose of helping readability. * It indicates that formal parameter is an output parameter. */ #ifndef OUT #define OUT #endif /** * This is a definition which has sole purpose of helping readability. * It indicates that formal parameter is both input and output parameter. */ #ifndef INOUT #define INOUT #endif /** * This is a definition which has sole purpose of helping readability. * It indicates that formal parameter is an optional parameter. */ #ifndef OPTIONAL #define OPTIONAL #endif
Et à l'utilisation:
Code : 1
2
3void bar( IN const Type& param_01, INOUT Type* param_02);
Après on peut étendre avec des INOUT_NOT_NULL par exemple et tester si l'IDE les garde lorsqu'il affiche dans son info-bulle le prototype de la fonctionle 15/07/2016 à 10:22 -
MédinocExpert éminent séniorIl n'y a qu'à regarder std::getline() pour voir le problème des paramètres modifiés par la fonction.
C'est pour ça que je préfère le modèle C#, où c'est explicitement signalé lors de l'appel; malheureusement il serait difficilement possible de causer ça en C++ sans briser la compatibilité. J'aime la proposition du template inout_param par contre, je vais probablement garder ça sous le coude pour mon propre code.le 16/07/2016 à 12:38 -
oodiniMembre émériteMais quel éditeur utilisez-vous donc ?
Sur le mien, si j'ai un doute en lisant un appel de fonction, il me suffit de passer la souris dessus pour voir sa signature dans un pop-up.
Et la question est réglée.le 21/07/2016 à 1:27 -
dragonjoker59Expert éminent séniorLa première solution de jo_link_noir n'est pas celle avec les macros, mais celle avec les types intermédiaires, donc là on ferait surtout confiance au compilateur, en fait (comme d'hab, quoi)le 21/07/2016 à 14:03
-
EhonnMembre chevronnéLe compilateur devrait optimiser tout ça.le 23/07/2016 à 23:44