Comment passer des objets aux fonctions en C ++?


249

Je suis nouveau dans la programmation C ++, mais j'ai de l'expérience en Java. J'ai besoin de conseils sur la façon de passer des objets à des fonctions en C ++.

Dois-je transmettre des pointeurs, des références ou des valeurs sans pointeur et sans 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.


6
De quel livre apprenez-vous le C ++?

17
Ce livre est fortement non recommandé. Optez pour C ++ Primer par Stan Lippman.
Prasoon Saurav

23
Eh bien, il y a votre problème. Schildt est fondamentalement cr * p - get Accelerated C ++ by Koenig & Moo.

9
Je me demande comment personne n'a mentionné le langage de programmation C ++ de Bjarne Stroustrup. Bjarne Stroustrup est le créateur de C ++. Un très bon livre pour apprendre le C ++.
George

15
@George: TC ++ PL n'est pas pour les débutants, mais il est considéré comme la Bible pour C ++. XD
Prasoon Saurav

Réponses:


277

Règles générales pour C ++ 11:

Passez par valeur , sauf lorsque

  1. vous n'avez pas besoin de posséder l'objet et un simple alias fera l'affaire, auquel cas vous passerez par constréférence ,
  2. vous devez muter l'objet, auquel cas utilisez pass par une constréférence non lvalue ,
  3. vous passez des objets de classes dérivées en tant que 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 par constréférence ou non.)

Passer par un pointeur n'est pratiquement jamais conseillé. Les paramètres facultatifs sont mieux exprimés en tant que std::optional( boost::optionalpour les anciennes bibliothèques std), et l'aliasing se fait bien par référence.

La sémantique de mouvement de C ++ 11 rend le passage et le retour en valeur beaucoup plus attrayants même pour des objets complexes.


Règles générales pour C ++ 03:

Passez les arguments par constréférence , sauf lorsque

  1. ils doivent être modifiés à l'intérieur de la fonction et ces changements doivent se refléter à l'extérieur, auquel cas vous passez par une non- constréférence
  2. la fonction doit pouvoir être appelée sans aucun argument, auquel cas vous passez par un pointeur, afin que les utilisateurs puissent passer NULL/ 0/ à la nullptrplace; appliquer la règle précédente pour déterminer si vous devez passer par un pointeur sur un constargument
  3. ils sont de types intégrés, qui peuvent être passés par copie
  4. ils doivent être modifiés à l'intérieur de la fonction et de tels changements ne doivent pas être reflétés à l'extérieur, auquel cas vous pouvez passer par 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 le passage par valeur crée toujours une copie en C ++ 03)


Il y a plus à cela, mais ces quelques règles pour débutants vous mèneront assez loin.


17
+1 - Je voudrais également noter que certains (c'est-à-dire Google) estiment que les objets qui seront modifiés dans la fonction doivent être passés via un pointeur au lieu d'une référence non const. Le raisonnement étant que lorsque l'adresse d'un objet est passée à une fonction, il est plus évident que cette fonction peut la changer. Exemple: Avec des références, l'appel est foo (bar); que la référence soit const ou non, avec un pointeur c'est foo (& bar); et il est plus apparent que foo passe devant un objet mutable.
RC.

19
@RC ne vous dit toujours pas si le pointeur est constant ou non. Les directives de Google ont fait leur apparition dans de nombreuses communautés en ligne C ++ - à juste titre, à mon humble avis.

14
Alors que dans d'autres contextes, Google peut montrer la voie, en C ++, leur guide de style n'est pas vraiment bon.
David Rodríguez - dribeas

4
@ArunSaha: En tant que guide de style pur, Stroustrup a un guide qui a été développé pour une entreprise aérospatiale. J'ai parcouru le guide Google et je ne l'ai pas aimé pour deux raisons. Sutter & Alexandrescu C ++ Coding Standards est un excellent livre à lire et vous pouvez obtenir pas mal de bons conseils, mais ce n'est pas vraiment un guide de style . Je ne connais aucun vérificateur automatisé de style , autre que les humains et le bon sens.
David Rodríguez - dribeas

3
@anon Cependant, vous avez la garantie que si un argument n'est pas passé via un pointeur, il n'est PAS modifié. C'est assez précieux à mon humble avis, sinon lorsque vous essayez de suivre ce qui arrive à une variable dans une fonction, vous devez examiner les fichiers d'en-tête de toutes les fonctions auxquelles elle est transmise pour déterminer si elle a changé. De cette façon, vous n'avez qu'à regarder ceux qui ont été passés via le pointeur.
smehmood

107

Il existe certaines différences dans les conventions d'appel en C ++ et Java. En C ++, il n'y a techniquement que deux conventions: pass-by-value et pass-by-reference, avec de la littérature incluant une troisième convention pass-by-pointer (qui est en fait pass-by-value d'un type de pointeur). En plus de cela, vous pouvez ajouter de la constance au type d'argument, améliorant 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 pas être nulle. Toutes les opérations effectuées à l'intérieur de la fonction s'appliquent à l'objet en dehors de la fonction. Cette convention n'est pas disponible en Java ou C.

Passer par valeur (et passer par pointeur)

Le compilateur générera une copie de l'objet dans le contexte d'appel et utilisera cette copie à l'intérieur de la fonction. Toutes les opérations effectuées à l'intérieur de la fonction sont effectuées sur la copie, pas sur l'élément externe. Il s'agit de la convention pour les types primitifs en Java.

Une version spéciale de celui-ci passe 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 d'objet à cet emplacement de mémoire, donc la fonction peut avoir des effets secondaires. L'effet de l'utilisation de la valeur de passage d'un pointeur sur l'objet permettra à la fonction interne de modifier les valeurs externes, comme avec la référence par passage et permettra également des valeurs facultatives (passez un pointeur nul).

C'est la convention utilisée en C lorsqu'une fonction a besoin de 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 référencé est le même: les modifications apportées à la référence / pointeur ne sont pas visibles à l'extérieur la fonction, mais les modifications apportées à la mémoire pointée sont.

Ajout de const à l'équation

En C ++, vous pouvez attribuer une constante aux objets lors de la définition de variables, de pointeurs et de références à différents niveaux. Vous pouvez déclarer une variable constante, vous pouvez déclarer une référence à une instance constante, et vous pouvez définir tous les pointeurs vers des objets constants, des pointeurs constants vers des objets mutables et des pointeurs constants vers des éléments constants. Inversement, 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 de référence à un élément immuable (sauf si la classe elle-même est immuable).

Ceci est largement utilisé dans les conventions d'appel C ++. Lorsque les objets sont petits, vous pouvez passer l'objet par valeur. Le compilateur va générer une copie, mais cette copie n'est pas une opération coûteuse. Pour tout autre type, si la fonction ne change 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 passera dans la fonction. Mais en même temps, le compilateur garantira que l'objet n'est pas modifié à l'intérieur de la fonction.

Règles de base

Voici quelques règles de base à suivre:

  • Préférez le passage par valeur pour les types primitifs
  • Préférez le passage par référence avec des références à la constante pour d'autres types
  • Si la fonction doit modifier l'argument, utilisez pass-by-reference
  • Si l'argument est facultatif, utilisez pass-by-pointer (à constant si la valeur facultative ne doit pas être modifiée)

Il existe d'autres petits écarts par rapport à ces règles, le premier étant la gestion 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 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 vers un élément différent, cela se fait généralement avec une sémantique de passage par pointeur, ou si possible avec des pointeurs intelligents.

Note d'accompagnement

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, pas un accesseur à celui-ci. L'exemple le plus simple consiste à implémenter une fonction d'échange:

// 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 d'échange ci-dessus modifie ses deux arguments par 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 passés par valeur dans les fonctions.


4
@ david-rodriguez-dribeas J'aime la section des règles générales, spécialement "Préférez la valeur de passage pour les types primitifs"
yadab

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

22

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 concerne principalement le style: voulez-vous que le code ressemble à call (obj) ou call (& obj) ? Cependant, il y a deux points où la différence est importante: le cas facultatif ci-dessous et vous souhaitez utiliser une référence lors de la surcharge des opérateurs.

... et en option

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 le cas intéressant. La règle générale est que les types "bon marché à copier" sont passés par valeur - ce sont généralement de petits types (mais pas toujours) - tandis que d'autres sont passés par const ref. Cependant, si vous devez faire une copie dans votre fonction, vous devez passer par valeur . (Oui, cela expose un peu de détails d'implémentation. C'est le C ++. )

... et en option

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 la moindre différence ici entre toutes les situations, alors choisissez celle qui vous facilite la vie.

Const by value est un détail d'implémentation

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

Ces déclarations sont en fait exactement la même fonction! Lors du passage par valeur, const est purement un détail d'implémentation. Essaye 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

3
+1 Je ne savais pas constêtre une implémentation lors du passage par valeur.
balki

20

Passez 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 les cycles CPU et la mémoire supplémentaire dépensée pour copier l'objet.

Passez par référence const:

void func (const vector& v);

Ce formulaire émule le comportement passe-par-valeur tout en supprimant la surcharge de copie. La fonction obtient un accès en lecture à l'objet d'origine, 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 à l'intérieur de la fonction pendant son exécution.

Passez par référence non const:

void func (vector& v)

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

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

Passer par le pointeur const:

void func (const vector* vp);

Fonctionnellement identique à passer par référence const, à 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.

Pas thread-safe.

Passer par un pointeur non const:

void func (vector* vp);

Similaire à la référence non const. L'appelant définit généralement la variable sur NULL lorsque la fonction n'est pas censée réécrire une valeur. Cette convention se retrouve dans de nombreuses API glibc. Exemple:

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

Tout comme tous passent par référence / pointeur, pas thread-safe.


0

Puisque personne n'a mentionné que j'ajoute dessus, 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 un qui crée un clone de l'objet, puis le transmettez à la méthode, donc lorsque vous modifiez les valeurs d'objet qui se refléteront sur la copie de l'objet au lieu de l'objet d'origine, c'est le problème en c ++, donc si vous faites de tous les attributs de classe des pointeurs, les constructeurs de copie copieront les adresses des attributs de pointeur, donc lorsque la méthode appelle sur l'objet qui manipule les valeurs stockées dans les adresses d'attributs de pointeur, les changements se reflètent également dans l'objet d'origine qui est passé en paramètre, donc cela peut se comporter de la même manière que Java mais n'oubliez pas que toute votre classe les attributs doivent être des pointeurs, vous devez également modifier les valeurs des pointeurs,sera beaucoup plus clair avec l'explication du 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 que c ++ ait des constructeurs de copie où vous créerez une nouvelle mémoire lorsque les objets contenant des pointeurs seront passés à des arguments de fonction qui arrêteront de manipuler les données d'autres objets, Java passe par valeur et la valeur est référence, donc il ne nécessite pas de constructeurs de copie.


-1

Il existe trois méthodes pour passer un objet à une fonction en tant que paramètre:

  1. Passer par référence
  2. passer par valeur
  3. ajout d'une constante dans le paramètre

Passez par l'exemple suivant:

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

Production:

Dis que je suis dans someFunc
La valeur du pointeur est -17891602
La valeur de la variable est 4


Il n'y a que 2 méthodes (les 2 premières que vous avez mentionnées). Aucune idée de ce que vous vouliez dire par "passer une constante dans le paramètre"
MM

-1

Voici les façons de passer des arguments / paramètres pour fonctionner en C ++.

1. par valeur.

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2. par référence.

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3. par objet.

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}

1
«passer par un objet» n'est pas une chose. Il n'y a que passer par valeur et passer par référence. Votre "cas 3" montre en fait un cas de passage par valeur et un cas de passage par référence.
MM
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.