269 votes

Comment passer des objets aux fonctions en C++ ?

Je suis novice en matière de programmation C++, mais j'ai de l'expérience en Java. J'ai besoin de conseils sur la manière de transmettre des objets aux fonctions en C++.

Dois-je transmettre des pointeurs, des références, ou des valeurs sans pointeur ni référence ? Je me souviens qu'en Java il n'y a pas de tels problèmes puisque nous passons juste la variable qui contient la référence aux objets.

Ce serait formidable si vous pouviez également expliquer où utiliser chacune de ces options.

7 votes

Dans quel livre apprenez-vous le C++ ?

1 votes

J'utilise Complete Reference:C++ de Herbert Schildt.

17 votes

Ce livre est fortement pas recommandé. Allez-y pour C++ Primer de Stan Lippman.

295voto

sbi Points 100828

Règles de base pour C++11 :

Passez par valeur sauf lorsque

  1. vous n'avez pas besoin de la propriété de l'objet et un simple alias fera l'affaire, dans ce cas vous passer const référence ,
  2. vous devez muter l'objet, dans ce cas, utilisez passer par un non const référence à la valeur l ,
  3. vous passez des objets de classes dérivées comme classes de base, auquel cas vous devez passer par référence . (Utilisez les règles précédentes pour déterminer s'il faut passer en const référence ou non).

Le passage par pointeur n'est pratiquement jamais conseillé. Les paramètres facultatifs sont mieux exprimés sous la forme d'un std::optional ( boost::optional pour les librairies std plus anciennes), et l'aliasing se fait bien par référence.

La sémantique de déplacement du C++11 rend le passage et le retour par valeur beaucoup plus attrayants, même pour les objets complexes.


Règles de base pour C++03 :

Passer les arguments par const référence sauf lorsque

  1. ils doivent être modifiés à l'intérieur de la fonction et ces modifications doivent être répercutées à l'extérieur, auquel cas il faut passer par non const référence
  2. la fonction doit pouvoir être appelée sans aucun argument, auquel cas vous passez par pointeur, afin que les utilisateurs puissent passer NULL / 0 / nullptr à la place ; appliquez la règle précédente pour déterminer si vous devez passe par un pointeur vers un const argument
  3. ils sont de types intégrés, qui peuvent être transmis par copie
  4. ils doivent être modifiés à l'intérieur de la fonction et ces modifications doivent pas être réfléchi à l'extérieur, auquel cas vous pouvez passer par la copie (une alternative serait de passer selon les règles précédentes et de faire une copie à l'intérieur de la fonction)

(ici, "passer par valeur" est appelé "passer par copie", car passer par valeur crée toujours une copie en C++03)


Ce n'est pas tout, mais ces quelques règles pour débutants vous mèneront assez loin.

18 votes

+1 - Je voudrais également noter que certains (par exemple Google) estiment que les objets qui seront modifiés dans la fonction devraient être transmis via un pointeur au lieu d'une référence non-const. Le raisonnement est le suivant : lorsque l'adresse d'un objet est transmise à une fonction, il est plus évident que cette fonction peut le modifier. Exemple : Avec des références, l'appel est foo(bar) ; que la référence soit constante ou non, avec un pointeur, c'est foo(&bar) ; et il est plus apparent que l'on passe à foo un objet mutable.

20 votes

@RC ne vous dit toujours pas si le pointeur est const ou non. Les directives de Google ont fait l'objet de nombreuses critiques dans les différentes communautés C++ en ligne - à juste titre, IMHO.

14 votes

Alors que dans d'autres contextes, Google peut montrer la voie, dans le cas du C++, son guide de style n'est pas vraiment bon.

115voto

Il existe quelques différences dans les conventions d'appel en C++ et en Java. En C++, il n'y a techniquement que deux conventions : pass-by-value et pass-by-reference, certaines publications incluant une troisième convention pass-by-pointer (qui est en fait pass-by-value d'un type pointeur). En plus de cela, vous pouvez ajouter une constance au type de l'argument, ce qui améliore la sémantique.

Passer par référence

Le passage par référence signifie que la fonction recevra conceptuellement votre instance d'objet et non une copie de celle-ci. La référence est conceptuellement un alias de l'objet qui a été utilisé dans le contexte d'appel, et ne peut être nulle. Toutes les opérations effectuées à l'intérieur de la fonction s'appliquent à l'objet à l'extérieur de la fonction. Cette convention n'est pas disponible en Java ou en C.

Pass by value (et pass-by-pointer)

Le compilateur va générer une copie de l'objet dans le contexte d'appel et utiliser cette copie dans la fonction. Toutes les opérations effectuées à l'intérieur de la fonction sont effectuées sur la copie, et non sur l'élément externe. C'est la convention pour les types primitifs en Java.

Une version spéciale de cette méthode consiste à passer un pointeur (adresse de l'objet) dans une fonction. La fonction reçoit le pointeur, et toutes les opérations appliquées au pointeur lui-même sont appliquées à la copie (pointeur), d'autre part, les opérations appliquées au pointeur déréférencé s'appliqueront à l'instance de l'objet à cet emplacement mémoire, donc la fonction peut avoir des effets secondaires. L'effet de l'utilisation de pass-by-value d'un pointeur vers l'objet permettra à la fonction interne de modifier les valeurs externes, comme avec pass-by-reference et permettra également des valeurs optionnelles (passer un pointeur nul).

C'est la convention utilisée en C lorsqu'une fonction doit modifier une variable externe, et la convention utilisée en Java avec les types de référence : la référence est copiée, mais l'objet pointé reste le même : les modifications de la référence/pointeur ne sont pas visibles en dehors de la fonction, mais celles de la mémoire pointée le sont.

Ajouter une constante à l'équation

En C++, vous pouvez attribuer la constance aux objets lors de la définition des variables, des pointeurs et des références à différents niveaux. Vous pouvez déclarer qu'une variable est constante, vous pouvez déclarer une référence à une instance constante, et vous pouvez définir tous les pointeurs vers des objets constants, les pointeurs constants vers des objets mutables et les pointeurs constants vers des éléments constants. À l'inverse, en Java, vous ne pouvez définir qu'un seul niveau de constance (mot-clé final) : celui de la variable (instance pour les types primitifs, référence pour les types de référence), mais vous ne pouvez pas définir une référence à un élément immuable (sauf si la classe elle-même est immuable).

Cette méthode est largement utilisée dans les conventions d'appel du C++. Lorsque les objets sont petits, vous pouvez passer l'objet par valeur. Le compilateur générera une copie, mais cette copie n'est pas une opération coûteuse. Pour tout autre type, si la fonction ne modifie pas l'objet, vous pouvez passer une référence à une instance constante (généralement appelée référence constante) du type. Cela ne copiera pas l'objet, mais le transmettra à la fonction. Mais en même temps, le compilateur garantira que l'objet ne sera pas modifié dans la fonction.

Règles de base

Voici quelques règles de base à suivre :

  • Préférer le pass-by-value pour les types primitifs
  • Préférer le pass-by-reference avec des références à des constantes pour d'autres types
  • Si la fonction doit modifier l'argument, utilisez la méthode pass-by-reference.
  • Si l'argument est optionnel, utilisez le pass-by-pointer (à constant si la valeur optionnelle ne doit pas être modifiée)

Il existe d'autres petites dérogations à ces règles, la première étant le traitement de la propriété d'un objet. Lorsqu'un objet est alloué dynamiquement avec new, il doit être désalloué avec delete (ou ses versions []). L'objet ou la fonction qui est responsable de la destruction de l'objet est considéré comme le propriétaire de la ressource. Lorsqu'un objet alloué dynamiquement est créé dans un morceau de code, mais que la propriété est transférée à un élément différent, cela se fait généralement avec la sémantique pass-by-pointer, ou si possible avec des pointeurs intelligents.

Note complémentaire

Il est important d'insister sur l'importance de la différence entre les références C++ et Java. En C++, les références sont conceptuellement l'instance de l'objet, et non un accesseur à celui-ci. L'exemple le plus simple est l'implémentation d'une fonction swap :

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

La fonction swap ci-dessus changements ses deux arguments grâce à l'utilisation de références. Le code le plus proche en Java :

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

La version Java du code modifiera les copies des références en interne, mais ne modifiera pas les objets réels en externe. Les références Java sont des pointeurs C sans arithmétique de pointeur qui sont transmis par valeur dans les fonctions.

4 votes

@david-rodriguez-dribeas J'aime la section sur les règles empiriques, en particulier "Préférer le pass-by-value pour les types primitifs".

0 votes

Selon moi, c'est une bien meilleure réponse à la question.

24voto

Il y a plusieurs cas à considérer.

Paramètre modifié (paramètres "out" et "in/out")

void modifies(T &param);
// vs
void modifies(T *param);

Ce cas est surtout une question de style : voulez-vous que le code ressemble à appel(obj) ou appel(&obj) ? Cependant, il y a deux points où la différence compte : le cas facultatif, ci-dessous, et le fait de vouloir utiliser une référence lors de la surcharge des opérateurs.

...et facultatif

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

Paramètre non modifié

void uses(T const &param);
// vs
void uses(T param);

C'est un cas intéressant. En règle générale, les types "bon marché à copier" sont transmis par valeur - ce sont généralement des petits types (mais pas toujours) - tandis que les autres sont transmis par const ref. Toutefois, si vous avez besoin de faire une copie au sein de votre fonction indépendamment, vous doit passer par valeur . (Oui, cela expose un peu de détails d'implémentation. C'est le C++. )

...et facultatif

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

Il y a ici le moins de différence entre toutes les situations, alors choisissez celle qui vous facilite la vie.

Const par valeur est un détail d'implémentation

void f(T);
void f(T const);

Ces déclarations sont en fait les exactement la même fonction ! Quand on passe par valeur, const est purement un détail d'implémentation. Essayez-le :

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types

22voto

nav Points 271

Passe par valeur :

void func (vector v)

Passez les variables par valeur lorsque la fonction a besoin d'une isolation complète de l'environnement, c'est-à-dire pour empêcher la fonction de modifier la variable d'origine ainsi que pour empêcher les autres threads de modifier sa valeur pendant l'exécution de la fonction.

L'inconvénient est que la copie de l'objet nécessite des cycles CPU et de la mémoire supplémentaire.

Passer par une référence constante :

void func (const vector& v);

Cette forme émule le comportement pass-by-value tout en supprimant la surcharge de copie. La fonction obtient un accès en lecture à l'objet original, mais ne peut pas modifier sa valeur.

L'inconvénient est la sécurité des threads : toute modification apportée à l'objet d'origine par un autre thread apparaîtra dans la fonction alors qu'elle est toujours en cours d'exécution.

Passe par une référence non-const :

void func (vector& v)

Utilisez cette option lorsque la fonction doit réécrire une valeur dans la variable, qui sera finalement utilisée par l'appelant.

Tout comme dans le cas de la référence const, ceci n'est pas thread-safe.

Passer par un pointeur constant :

void func (const vector* vp);

Fonctionnellement identique à pass by const-reference à l'exception de la syntaxe différente, plus le fait que la fonction appelante peut passer le pointeur NULL pour indiquer qu'elle n'a pas de données valides à passer.

Non sécurisé par les fils.

Passage par un pointeur non-const :

void func (vector* vp);

Similaire à la référence non-const. L'appelant met généralement la variable à NULL lorsque la fonction n'est pas censée renvoyer une valeur. Cette convention est vue dans de nombreuses API de la glibc. Exemple :

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

Comme toutes les passes par référence/pointeur, non thread-safe.

0voto

murali krish Points 511

Puisque personne ne l'a mentionné, j'ajoute quelque chose. Lorsque vous passez un objet à une fonction en c++, le constructeur de copie par défaut de l'objet est appelé si vous n'en avez pas qui crée un clone de l'objet et le passe ensuite à la méthode, donc lorsque vous changez les valeurs de l'objet, cela se reflète sur la copie de l'objet au lieu de l'objet original, c'est le problème en c++, alors les constructeurs de copie copieront les adresses des attributs de pointeur, ainsi quand les invocations de méthode sur l'objet qui manipule les valeurs stockées dans les adresses d'attributs de pointeur, les changements reflètent également dans l'objet original qui est passé comme un paramètre, ainsi ceci peut se comporter comme un Java mais n'oubliez pas que tous vos attributs de classe doivent être des pointeurs, également vous devriez changer les valeurs des pointeurs, sera beaucoup plus clair avec l'explication de code.

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

Mais ce n'est pas une bonne idée car vous finirez par écrire beaucoup de code impliquant des pointeurs, qui sont sujets à des fuites de mémoire et n'oubliez pas d'appeler des destructeurs. Et pour éviter cela, c++ a des constructeurs de copie où vous créerez une nouvelle mémoire lorsque les objets contenant des pointeurs sont passés en arguments de fonction, ce qui empêchera de manipuler les données d'autres objets. Java passe par valeur et la valeur est une référence, donc il ne nécessite pas de constructeurs de copie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X