web-dev-qa-db-fra.com

Comment passer des objets à des fonctions en C ++?

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

Dois-je passer des pointeurs, des références ou des valeurs non-pointeur et non-référence? Je me souviens que dans Java, il n’existait pas de tels problèmes puisque nous passions simplement à la variable qui contient la référence aux objets.

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

241
Rakesh K

Règles de base pour C++ 11:

Passer par valeur , sauf quand

  1. vous n'avez pas besoin de la propriété de l'objet et un alias simple suffira, auquel cas vous passez par const reference ,
  2. vous devez muter l'objet, auquel cas, utilisez et passez par une référence non -const lvalue ,
  3. vous transmettez des objets de classes dérivées en tant que classes de base, auquel cas vous devez transmettre par référence . (Utilisez les règles précédentes pour déterminer s'il faut passer par const référence ou non.)

Passer par le 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 anciennes bibliothèques std), et l'aliasing est bien défini par référence.

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


Règles de base pour C++ 03:

Passer des arguments par const reference , sauf lorsque

  1. ils doivent être changés à l'intérieur de la fonction et ces changements doivent être reflétés à l'extérieur, auquel cas vous passez par non -const reference
  2. la fonction doit être appelable sans aucun argument, auquel cas vous passez par pointeur pour que les utilisateurs puissent passer NULL/0/nullptr à la place; applique la règle précédente pour déterminer si vous devez passer par un pointeur sur un argument const
  3. ils sont de types intégrés, qui peuvent être passés par copie
  4. elles doivent être modifiées à l'intérieur de la fonction et ces modifications doivent pas être reflétées à 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, "passe par valeur" est appelé "passe par copie", car passer par valeur crée toujours une copie en C++ 03)


Il y a plus que cela, mais ces quelques règles de débutant vous mèneront assez loin.

267
sbi

Il existe certaines différences dans les conventions d'appel en C++ et Java. En C++, techniquement, il n'y a que deux conventions: pass-by-value et pass-by-reference, avec une littérature incluant une troisième convention de pass-by-pointeur (qui correspond en fait à une valeur passe d'un type de pointeur). En plus de cela, vous pouvez ajouter une const-ness au type de l'argument, améliorant ainsi la sémantique.

passage par référence

Passer 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 utilisé dans le contexte de l'appel et ne peut pas être null. Toutes les opérations effectuées dans la fonction s'appliquent à l'objet en dehors de la fonction. Cette convention n'est pas disponible dans Java ou C.

passage par valeur (et passage par pointeur)

Le compilateur générera une copie de l'objet dans le contexte de l'appel et utilisera cette copie dans la fonction. Toutes les opérations effectuées dans 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 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). Par contre, les opérations appliquées au pointeur déréférencé s’appliqueront à l’instance d’objet située à cet emplacement mémoire. La fonction peut avoir des effets secondaires. L'effet d'utiliser le paramètre point par point d'un pointeur sur l'objet permettra à la fonction interne de modifier les valeurs externes, comme avec le paramètre passe-à-référence, et autorisera également des valeurs facultatives (passer un pointeur null).

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

Ajout de const à l'équation

En C++, vous pouvez attribuer une constance 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 définir tous les pointeurs sur des objets constants, des pointeurs constants sur des objets modifiables et des pointeurs constants sur des éléments constants. Inversement, dans Java, vous ne pouvez définir qu'un seul niveau de constante (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).

Ceci est largement utilisé dans les conventions d'appel C++. Lorsque les objets sont petits, vous pouvez les transmettre 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 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 transmettra à la fonction. Mais en même temps, le compilateur garantira que l'objet n'est pas modifié dans la fonction.

Règles de base

Voici quelques règles de base à suivre:

  • Préfère passer par valeur pour les types primitifs
  • Préfère passer par référence avec des références à constante pour les autres types
  • Si la fonction a besoin de modifier l'argument, utilisez passe par référence
  • Si l'argument est facultatif, utilisez passe-par-pointeur (constant si la valeur facultative ne doit pas être modifiée)

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

note latérale

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. L’exemple le plus simple est l’implémentation d’une fonction de 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 change ses deux arguments en utilisant des 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 des fonctions.

105

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 optionnel, ci-dessous, et vous souhaitez utiliser une référence pour surcharger les 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 le cas intéressant. La règle empirique est que les types "bon marché à copier" sont passés par valeur - ce sont généralement des types petits (mais pas toujours) - tandis que d'autres sont passés par const ref. Cependant, si vous avez besoin de faire une copie dans votre fonction malgré tout, vous devez passer par valeur . (Oui, cela expose un peu les détails de l'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 la moindre différence entre toutes les situations, alors choisissez celle qui vous simplifie 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 de 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
22
Roger Pate

Pass par valeur:

void func (vector v)

Transmettez les variables par valeur lorsque la fonction nécessite 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 d'autres threads de modifier sa valeur pendant l'exécution de la fonction.

L'inconvénient est les cycles du processeur et la mémoire supplémentaire utilisée pour copier l'objet.

Passer par référence constante:

void func (const vector& v);

Ce formulaire émule le comportement passage par valeur tout en supprimant le temps système nécessaire à la 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 dans la fonction pendant son exécution.

Passer par référence non-const:

void func (vector& v)

Utilisez ceci lorsque la fonction doit é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 à transmettre.

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 écrire une valeur. Cette convention est visible 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
    }
}

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

19
nav

Puisque personne n’a mentionné que j’ajoute dessus, lorsque vous transmettez 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. Lorsque vous modifiez les valeurs d'objet qui se répercuteront sur la copie de l'objet au lieu de l'objet d'origine, le problème se pose en c ++. Par conséquent, si vous définissez tous les attributs de classe comme des pointeurs, les constructeurs de copie copient les adresses du attributs de pointeur, donc lorsque les invocations de méthode sur l'objet manipulant les valeurs stockées dans les adresses d'attributs de pointeur, les modifications sont également répercutées sur l'objet d'origine qui est transmis en tant que paramètre, de sorte qu'il peut se comporter de la même manière que Java n'oubliez pas que tous vos attributs de classe doivent être des pointeurs, vous devez également changer les valeurs des pointeurs, cela 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, sujets aux 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 seront passés à des arguments de fonction qui cesseront de manipuler les données des objets, Java passe par valeur et valeur est référence, de sorte ne nécessite pas de constructeur de copie.

0
murali krish