Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

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 , par Pyramidev

0PARTAGES

1  0 
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
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
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
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
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 ?

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de jo_link_noir
Membre expert https://www.developpez.com
Le 14/07/2016 à 23:48
J'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));.
4  0 
Avatar de JolyLoic
Rédacteur/Modérateur https://www.developpez.com
Le 15/07/2016 à 1:31
Certains 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 : Sélectionner tout
1
2
3
4
std::list<int> l1, l2;
/*...*/
auto it = l1.find(/* ...*/);
l1.splice(it, l2);
Le gars qui n'a pas compris que l2 va être modifié par cet appel n'a clairement pas compris ce que fait la fonction splice... Et si tel est le cas, je ne pense pas qu'ajouter quelque décoration que ce soit au site d'appel va l'aider.
3  0 
Avatar de Médinoc
Expert éminent sénior https://www.developpez.com
Le 21/07/2016 à 7:25
Citation Envoyé par oodini Voir le message
Mais 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.
  1. L'objectif, c'est de voir du premier coup d'œil, donc "passer la souris dessus" ne remplit pas cet objectif.
  2. 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.
3  0 
Avatar de Ehonn
Membre chevronné https://www.developpez.com
Le 21/07/2016 à 19:22
Citation Envoyé par r0d Voir le message
Si c'est juste une question de lisibilité, alors il suffit d'utiliser une convention de nommage. Par exemple iMaVariable, oMaVariable, ioMaVariable.
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 : Sélectionner tout
1
2
3
T ioT;
std::cin << ioT; // Devrait être oT;
std::cout >> ioT; // Devrait être iT;
Code : Sélectionner tout
1
2
3
4
5
T t;
T const & iT = t;
T & oT = t;
std::cin << oT;
std::cout >> iT;
PS : Je viens de remarquer que IN, OUT, IN/OUT est très bien lors de la déclaration de fonction mais pas lors de l'appel (exemple : std::cin prend la variable en OUT). Du coup, je préfère des mots comme const et mutable, par exemple READ, WRITE, READ/WRITE.
3  0 
Avatar de Pyramidev
Expert confirmé https://www.developpez.com
Le 15/07/2016 à 1:08
Bonne idée !

Ça donnerait quelque chose du genre :

inout.h :
Code : Sélectionner tout
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);
}
Dans le fichier X :
Code : Sélectionner tout
void foo(const Type& paramIn, inout<Type> paramInOut);
Dans le fichier Y :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
void uneFonction()
{
    Type obj1, obj2, *ptr1, *ptr2;
 
    // ...
 
    foo(obj1, make_inout(obj2));
    if(ptr1 != nullptr && ptr2 != nullptr) {
        foo(*ptr1, make_inout(*ptr2));
    }
}
EDIT : ajout de opt_inout dans inout.h pour prendre en compte le cas des paramètres IN/OUT optionnels (pointeurs vers type non constant qui peuvent être nuls).
2  0 
Avatar de foetus
Expert éminent https://www.developpez.com
Le 15/07/2016 à 10:22
Et pourquoi pas "un truc à l'ancienne" couplé avec tes recommandations

Le truc à l'ancienne:
Code : Sélectionner tout
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 : Sélectionner tout
1
2
3
void 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 fonction
2  0 
Avatar de Médinoc
Expert éminent sénior https://www.developpez.com
Le 16/07/2016 à 12:38
Il 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.
2  0 
Avatar de oodini
Membre émérite https://www.developpez.com
Le 21/07/2016 à 1:27
Mais 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.
2  0 
Avatar de dragonjoker59
Expert éminent sénior https://www.developpez.com
Le 21/07/2016 à 14:03
La 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)
2  0 
Avatar de Ehonn
Membre chevronné https://www.developpez.com
Le 23/07/2016 à 23:44
Le compilateur devrait optimiser tout ça.
2  0