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
. 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
?
3 Réponses :
Votre question comporte plusieurs parties différentes, et j'aborderai chacune d'elles séparément.
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.
Passer quelque chose par référence présente quelques avantages.
->
. 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.
Si vous souhaitez modifier le bouton , passez la QAction par référence:
QAction* modifyPointer(QAction* buttonPointer) { return buttonPointer + 1; }
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é.
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.
C'était de loin la réponse la plus utile et expliquait clairement tout ce qui m'était confus.
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.