1
votes

Y a-t-il un avantage à utiliser reference_wrapper sur un pointeur par rapport au passage du pointeur par référence?

Dans QT, j'utilise un objet QAction pour activer / désactiver les boutons de menu lorsque l'utilisateur clique dessus. Il m'est venu à l'esprit que j'écrivais la même fonctionnalité pour chaque cas, alors je l'ai transformée en une fonction avec une table de hachage privée qui ferait tout le contrôle de l'activation et de la désactivation des boutons de menu. En d'autres termes, ma table de hachage ressemble à std :: unordered_map table . Où le booléen est la clé et l'objet est la valeur que je mets à jour. J'ai donc écrit la fonction suivante:

updateButton(std::ref(ui->actionDoAction));

Donc chaque fois que je cliquais sur un élément de menu. J'appellerais cette fonction tout en haut:

void updateButton(std::reference_wrapper<QAction*> currentButton)
{
   ...
}

J'ai alors réalisé que je passais le pointeur par valeur. Et comme j'ai tellement de boutons que je dois gérer, je ne veux pas faire de copies si je peux l'éviter. À ce stade, j'ai totalement oublié que je pouvais faire QAction * & currentButton pour passer le pointeur par référence. Alors j'ai commencé à chercher et j'ai trouvé std :: reference_wrapper . J'ai donc changé la fonction en:

void on_action_menuItem1_triggered()
{
  updateButton(ui->actionDoAction);

  ...
}

et l'appelant par:

void updateButton(QAction *currentButton)
{
  if(!table.empty())
  {
    // Enable the last menu item you clicked on and disable the current menu item.
    table[false]->setEnabled(true);
    currentButton->setEnabled(false);
    table[false] = currentButton;
  }
  else
  {
    // Menu item was clicked for the first time
    currentButton->setEnabled(false);
    table[false] = currentButton;
  }
} 

Y a-t-il un avantage à le faire de cette façon plutôt que QAction * & currentButton ?


9 commentaires

Les pointeurs sont de petits objets, peu coûteux à copier, vous pouvez les passer par valeur.


std :: reference_wrapper sous le bois est également un pointeur.


Utilisez uniquement QAction * & si vous devez modifier le pointeur lui-même.


@ Jarod42 Je modifie un état dans le pointeur. Je désactive et active le bouton et peut-être que vous avez raison. Il peut être bon marché de copier le pointeur, cependant si je veux être efficace. Ne devrais-je pas le transmettre par référence car je n'ai pas nécessairement besoin d'une copie? J'ai juste besoin de mettre à jour une chose. Et copier tout l'objet de menu pour mettre à jour la seule chose me semble un peu exagéré.


@Sailanarmo Passer un pointeur par valeur ne copie pas l'objet, il copie uniquement le pointeur.


@super, vous avez raison. Je me suis mal exprimé.


"Je modifie un état dans le pointeur" - j'en doute. Vous modifiez peut-être quelque chose sur lequel le pointeur pointe , mais je serais très surpris si vous modifiiez des parties de la variable du pointeur elle-même. Veuillez être spécifique et précis dans les mots que vous utilisez pour décrire vos actions.


@JesperJuhl donc dans ce cas. Il n'est pas nécessaire de passer le pointeur par référence?


@Sailanarmo la seule raison de passer le pointeur par référence serait si vous vouliez modifier le pointeur lui-même ( sur quoi il pointe, dans l'appelant). Donc non.


3 Réponses :


0
votes

Votre question comporte plusieurs parties différentes, et j'aborderai chacune d'elles séparément.

Combien coûte le passage d'un pointeur?

Passer un pointeur est fondamentalement gratuit strong >. Si la fonction à laquelle vous le transmettez n'a pas beaucoup de paramètres, alors le compilateur passera le pointeur dans un registre CPU, ce qui signifie que la valeur du pointeur n'a même jamais besoin d'être copiée sur la pile.

Combien coûte le passage par référence?

Passer quelque chose par référence présente quelques avantages.

  • Une référence vous permet de supposer les points de référence vers un objet valide (vous n'avez donc pas à vérifier si l'adresse de la référence est nulle).
  • Lorsque vous passez par référence, vous pouvez l'utiliser comme s'il s'agissait d'un objet normal; pas besoin d'utiliser la syntaxe -> .
  • Le compilateur peut (parfois) générer un programme plus rapide car il sait que la référence ne peut pas être réaffectée.

Cependant, sous le capot, une référence est passée de la même manière qu'un pointeur . Les deux ne sont que des adresses. Et les deux sont fondamentalement gratuits.

Lequel devriez-vous utiliser?

  • Si vous transmettez un objet volumineux que vous n'avez pas à modifier, transmettez-le comme référence const.
  • Si vous transmettez un petit objet que vous n'avez pas besoin de modifier, transmettez-le par valeur
  • Si vous transmettez une référence que vous devez modifier, transmettez-la par référence.

Qu'est-ce que modifiez-vous? (Le bouton, ou le pointeur vers le bouton?)

Si vous souhaitez modifier le bouton , passez la QAction par référence:

QAction* modifyPointer(QAction* buttonPointer) {
    return buttonPointer + 1;
}


0 commentaires

0
votes

Si vous passez des pointeurs comme arguments, dans presque tous les cas, vous voudrez les passer par valeur. Les performances seront identiques, car chaque compilateur majeur C ++ traite de toute façon les références en interne comme des pointeurs.

Ou dites-vous que vous pensez qu'une COPIE D'OBJET est créée lorsque vous passez un pointeur par valeur? Ce n'est pas le cas - vous pouvez passer un pointeur autant que vous le souhaitez, aucun nouvel objet ne sera alloué.


0 commentaires

1
votes

Réponse courte

Il n'y a aucun avantage à passer par référence au pointeur plutôt que par pointeur, sauf si vous souhaitez modifier le pointeur d'origine lui-même.

Réponse longue

En fonction de la façon dont votre question est formulée, ainsi que de votre première réponse aux commentaires à votre question, vous avez un malentendu fondamental sur ce que sont réellement les pointeurs et les références. Contrairement à ce que vous semblez croire, ce ne sont pas les objets eux-mêmes.

Revenons un peu aux fondamentaux.

Il y a deux zones principales de la mémoire auxquelles nous avons accès dans pour stocker les données, la mémoire Stack et la mémoire Heap.

Lorsque nous déclarons des variables, nous obtenons de l'espace alloué dans la mémoire Stack et nous pouvons accéder aux données en utilisant les noms de variables. La mémoire est automatiquement désallouée lorsque la variable est hors de portée.

void myFunction(){
    QString *str = new QString("First string");  // str is allocated in Stack memory and assigned the memory location to a new QString object allocated in Heap memory
    substituteString(str);                       
    delete str;                                  // de-allocate the memory of QString("Second String"). 'str' now points to an invalid memory location
} // str is de-allocated automatically from Stack memory

void substituteString(QString *&externalString){  // 'externalString' is allocated in Stack memory and contains memory location of 'str' from MyFunction()
   delete externalString;                        // de-allocate the Heap memory of QString("First string"). 'str' now points to an invalid location
   externalString = new QString("Second string"); // Allocate new Heap memory for a new QString object. 'str' in MyFunction() now points to this new location
} // externalString is de-allocated automatically from Stack memory

Le plus gros problème est que la quantité de mémoire Stack est extrêmement limitée par rapport aux besoins normaux du programme et fait que vous avez souvent besoin que les données persistent en dehors de certaines étendues que la création d'instances de grandes classes dans la mémoire Stack est généralement une mauvaise idée. C'est là que le Heap devient utile.

Malheureusement, avec la mémoire Heap, vous devez prendre en charge la durée de vie de l'allocation de mémoire. Vous n'avez pas non plus d'accès direct aux données de la mémoire Heap, vous avez besoin d'une sorte de tremplin pour y arriver. Vous devez explicitement demander au système d'exploitation une allocation de mémoire, puis lui dire explicitement de désallouer la mémoire lorsque vous avez terminé. C ++ nous donne deux opérateurs pour cela: new et delete.

MyClass &instance
MyClass * const instance

Comme vous semblez clairement le comprendre, dans ce L'instance de cas est connue sous le nom de pointeur . Ce que vous ne semblez pas réaliser, c'est qu'un pointeur n'est pas une instance d'un objet lui-même, c'est le "tremplin" vers l'objet. Le but d'un pointeur est de contenir cette adresse mémoire afin de ne pas la perdre et de nous permettre d'accéder à cette mémoire en dé-référençant l'emplacement mémoire.

En C ++, il y a deux façons de faire cela: soit vous dé-référencez le pointeur entier, puis accédez aux membres de l'objet comme vous le feriez avec un objet sur la pile; ou vous utiliseriez l'opérateur de déréférencement de membre et accéderiez au membre qui l'utilise.

void myFunction(){
    MyClass *instance = new MyClass();  // 'instance' is allocated on the Stack, and assigned memory location of new Heap allocation
    instance->doSomething();
    AnotherFunction(instance);
    delete instance;                    // Heap memory pointed to is explicitly de-allocated
} // 'instance' is automatically de-allocated on Stack

void anotherFunction(MyClass *inst){ // 'inst' is a new pointer-to-MyClass on the Stack with a copy of the memory location passed in
    inst->doSomethingElse();
} // 'inst' is automatically de-allocted

Les pointeurs eux-mêmes ne sont que des instances spéciales d'entiers. Les valeurs qu'ils contiennent sont les adresses de mémoire dans la mémoire du tas où vous allouez vos objets. Leur taille dépend de la plate-forme pour laquelle vous avez compilé (généralement 32 bits ou 64 bits) donc les transmettre n'est rien de plus cher que de passer un entier.

Je ne saurais trop insister sur le fait que les variables de pointeur sont pas les objets eux-mêmes, ils sont alloués dans la mémoire de pile et se comportent exactement comme n'importe quelle autre variable de pile quand ils sortent de la portée.

void myFunction() {
    MyClass *instance = new MyClass();         // pointer-sized integer of type 'pointer-to-MyClass' created in Stack memory
    instance->doSomething();
}  // instance is automatically de-allocated when going out of scope. 
// Oops! We didn't explicitly de-allocate the object that 'instance' was pointing to 
// so we've lost knowledge of it's memory location. It is still allocated in Heap 
// memory but we have no idea where anymore so that memory is now 'leaked'

Maintenant, parce que sous-le-capot, les pointeurs ne sont rien de plus que des entiers spéciaux, les passant comme pas plus chers que de passer tout autre type d'entier.

void myFunction(){
    MyClass *instance = new MyClass();
    (*instance).doSomething();          // older-style dereference-then-member-access
    instance->doSomethingElse();        // newer-style using member dereference operator
}

Jusqu'à présent, je n'ai pas mentionné de références, car ils sont en gros les mêmes que les pointeurs. Ce ne sont également que des entiers sous le capot, mais ils simplifient l'utilisation en rendant la syntaxe d'accès aux membres la même que celle des variables Stack. Contrairement aux pointeurs ordinaires, les références doivent être initialisées avec un emplacement mémoire valide et cet emplacement ne peut pas être modifié.

Ce qui suit est fonctionnellement équivalent:

void myFunction(){
   MyClass *instance = new MyClass();  // ask OS for memory from heap
   instance->doSomething();
   delete instance;                    // tell OS that you don't need the heap memory anymore  
}

Les références aux pointeurs sont des doubles indirections, ce sont essentiellement des pointeurs vers des pointeurs et sont utiles si vous voulez être capable de manipuler, non seulement l'objet Heap, mais aussi le pointeur contenant l'emplacement mémoire vers cet objet de tas.

void myFunction() {
    MyClass instance;         // allocated in Stack memory
    instance.doSomething();

}   // instance gets automatically de-allocated here

Si je me suis expliqué clairement, et que vous avez suivi moi jusqu'à présent, vous devez maintenant comprendre que, dans votre cas, lorsque vous passez un pointeur vers QAction à une fonction, vous ne copiez pas l'objet QAction , vous ' ne copiez que le pointeur vers cet emplacement mémoire. Étant donné que les pointeurs ne sont que des entiers sous le capot, vous ne copiez que quelque chose de 32 bits ou 64 bits (en fonction des paramètres de votre projet), et changer cela en référence à un pointeur ne fera absolument aucune différence.


1 commentaires

C'était de loin la réponse la plus utile et expliquait clairement tout ce qui m'était confus.