Que signifie «int & foo ()» en C ++?


114

En lisant cette explication sur les valeurs lvalues ​​et rvalues, ces lignes de code me sont apparues:

int& foo();
foo() = 42; // OK, foo() is an lvalue

Je l'ai essayé dans g ++, mais le compilateur dit "référence indéfinie à foo ()". Si j'ajoute

int foo()
{
  return 2;
}

int main()
{
  int& foo();
  foo() = 42;
}

Il compile bien, mais son exécution donne une erreur de segmentation . Juste la ligne

int& foo();

par lui-même à la fois compile et s'exécute sans aucun problème.

que veut dire ce code? Comment attribuer une valeur à un appel de fonction et pourquoi n'est-ce pas une rvalue?


25
Vous n'attribuez pas de valeur à un appel de fonction , vous affectez une valeur à la référence qu'elle renvoie .
Frédéric Hamidi

3
@ FrédéricHamidi attribuant une valeur à l'objet auquel la référence renvoyée fait référence
MM

3
Oui, c'est un alias pour l'objet; la référence n'est pas l'objet
MM

2
Les déclarations de fonction locale @RoyFalk sont une pox, personnellement, je serais heureux de les voir supprimées de la langue. (Hors lambdas bien sûr)
MM

4
Il y a déjà un bogue GCC pour cela .
jotik le

Réponses:


168

L'explication suppose qu'il existe une implémentation raisonnable pour foolaquelle renvoie une référence lvalue à une valeur valide int.

Une telle implémentation pourrait être:

int a = 2; //global variable, lives until program termination

int& foo() {
    return a;
} 

Maintenant, puisque foorenvoie une référence lvalue, nous pouvons assigner quelque chose à la valeur de retour, comme ceci:

foo() = 42;

Cela mettra à jour le global aavec la valeur 42, que nous pouvons vérifier en accédant directement à la variable ou en appelant à foonouveau:

int main() {
    foo() = 42;
    std::cout << a;     //prints 42
    std::cout << foo(); //also prints 42
}

Raisonnable comme dans l'opérateur []? Ou d'autres méthodes d'accès aux membres?
Jan Dorniak

8
Compte tenu de la confusion concernant les réfrences, il est probablement préférable de supprimer les variables locales statiques des échantillons. L'initialisation ajoute une complexité inutile, ce qui est un obstacle à l'apprentissage. Faites-en simplement un mondial.
Mooing Duck

72

Toutes les autres réponses déclarent un statique à l'intérieur de la fonction. Je pense que cela pourrait vous dérouter, alors jetez un œil à ceci:

int& highest(int  & i, int  & j)
{
    if (i > j)
    {
        return i;
    }
    return j;
}

int main()
{
    int a{ 3};
    int b{ 4 };
    highest(a, b) = 11;
    return 0;
}

Étant donné que highest()renvoie une référence, vous pouvez lui attribuer une valeur. Quand cela s'exécute, bsera changé en 11. Si vous avez changé l'initialisation de sorte que ac'était, disons, 8, alors aserait changé en 11. C'est un code qui pourrait en fait servir un but, contrairement aux autres exemples.


5
Y a-t-il une raison d'écrire int a {3} au lieu de int a = 3? Cette syntaxe ne me semble pas appropriée.
Aleksei Petrenko le

6
@AlexPetrenko c'est l'initialisation universelle. Je l'ai utilisé dans l'exemple que j'avais sous la main. Rien d'inapproprié à ce sujet
Kate Gregory

3
Je sais ce que c'est. a = 3 semble tellement plus propre. Préférence personnelle)
Aleksei Petrenko

Tout comme déclarer statique à l'intérieur d'une fonction peut dérouter certaines personnes, je pense également que l'utilisation de l'initialisation universelle est aussi susceptible de le faire.
ericcurtin

@AlekseiPetrenko Dans le cas de int a = 1,3, il se convertit implicitement en a = 1. Alors que int a {1.3} entraîne une erreur: réduction de la conversion.
Sathvik le

32
int& foo();

Déclare une fonction nommée foo qui renvoie une référence à un int. Ce que ces exemples ne parviennent pas à faire, c'est de vous donner une définition de cette fonction que vous pourriez compiler. Si nous utilisons

int & foo()
{
    static int bar = 0;
    return bar;
}

Nous avons maintenant une fonction qui renvoie une référence à bar. puisque bar est, staticil vivra après l'appel à la fonction, il est donc sûr de lui renvoyer une référence. Maintenant si nous faisons

foo() = 42;

Ce qui se passe, c'est que nous attribuons 42 à barpuisque nous attribuons à la référence et que la référence n'est qu'un alias pour bar. Si nous appelons à nouveau la fonction comme

std::cout << foo();

Il imprimerait 42 puisque nous avons défini barcelui ci-dessus.


15

int &foo();déclare une fonction appelée foo()avec le type de retour int&. Si vous appelez cette fonction sans fournir de corps, vous obtiendrez probablement une erreur de référence non définie.

Dans votre deuxième tentative, vous avez fourni une fonction int foo(). Cela a un type de retour différent de la fonction déclarée par int& foo();. Vous avez donc deux déclarations identiques fooqui ne correspondent pas, ce qui enfreint la règle d'une définition, provoquant un comportement indéfini (aucun diagnostic requis).

Pour quelque chose qui fonctionne, supprimez la déclaration de fonction locale. Ils peuvent conduire à un comportement indéfini silencieux comme vous l'avez vu. Au lieu de cela, utilisez uniquement des déclarations de fonction en dehors de toute fonction. Votre programme pourrait ressembler à:

int &foo()
{
    static int i = 2;
    return i;
}  

int main()
{
    ++foo();  
    std::cout << foo() << '\n';
}

10

int& foo();est une fonction renvoyant une référence à int. Votre fonction fournie est renvoyée intsans référence.

Vous pouvez faire

int& foo()
{
    static int i = 42;
    return i;
}

int main()
{
    int& foo();
    foo() = 42;
}

7

int & foo();signifie que foo()renvoie une référence à une variable.

Considérez ce code:

#include <iostream>
int k = 0;

int &foo()
{
    return k;
}

int main(int argc,char **argv)
{
    k = 4;
    foo() = 5;
    std::cout << "k=" << k << "\n";
    return 0;
}

Ce code imprime:

$ ./a.out k = 5

Parce que foo()renvoie une référence à la variable globale k.

Dans votre code révisé, vous convertissez la valeur renvoyée en référence, qui est alors invalide.


5

Dans ce contexte, le & signifie une référence - donc foo renvoie une référence à un int, plutôt qu'à un int.

Je ne sais pas si vous avez déjà travaillé avec des pointeurs, mais c'est une idée similaire, vous ne renvoyez pas réellement la valeur hors de la fonction - au lieu de cela, vous transmettez les informations nécessaires pour trouver l'emplacement en mémoire où cela int est.

Donc, pour résumer, vous n'attribuez pas de valeur à un appel de fonction - vous utilisez une fonction pour obtenir une référence, puis vous affectez la valeur référencée à une nouvelle valeur. Il est facile de penser que tout se passe en même temps, mais en réalité, l'ordinateur fait tout dans un ordre précis.

Si vous vous demandez - la raison pour laquelle vous obtenez une erreur de segmentation est parce que vous renvoyez un littéral numérique '2' - c'est donc l'erreur exacte que vous obtiendriez si vous deviez définir un const int puis essayer de le modifier valeur.

Si vous n'avez pas encore appris sur les pointeurs et la mémoire dynamique, je vous le recommanderais d'abord car il y a quelques concepts que je pense difficiles à comprendre à moins que vous ne les appreniez tous en même temps.


4

L'exemple de code sur la page liée n'est qu'une déclaration de fonction factice. Il ne compile pas, mais si vous aviez défini une fonction, cela fonctionnerait généralement. L'exemple signifiait "Si vous aviez une fonction avec cette signature, vous pourriez l'utiliser comme ça".

Dans votre exemple, foorenvoie clairement une lvalue basée sur la signature, mais vous retournez une rvalue qui est convertie en lvalue. Ceci est clairement déterminé à échouer. Vous pourriez faire:

int& foo()
{
    static int x;
    return x;
}

et réussirait en changeant la valeur de x, en disant:

foo() = 10;

3

La fonction que vous avez, foo (), est une fonction qui renvoie une référence à un entier.

Donc, disons qu'à l'origine foo a renvoyé 5, et plus tard, dans votre fonction principale, dites-vous foo() = 10;, puis affiche foo, il affichera 10 au lieu de 5.

J'espère que cela à du sens :)

Je suis également nouveau dans la programmation. C'est intéressant de voir des questions comme celle-ci qui font réfléchir! :)

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.