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 :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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);
    }
}
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 :
  • 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 - Visualiser dans une fenêtre à part
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);
    }
}
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 :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 !
    }
}
Exemple 2 :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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);
    }
}
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 ?