web-dev-qa-db-fra.com

La pratique consistant à renvoyer une variable de référence C ++ est-elle néfaste?

C'est un peu subjectif je pense; Je ne sais pas si l'avis sera unanime (j'ai vu beaucoup d'extraits de code dans lesquels des références sont renvoyées).

Selon un commentaire sur cette question que je viens de poser concernant les références d'initialisation , renvoyer une référence peut être mauvais car, si j'ai bien compris, il est plus facile de rater de la supprimer, ce qui peut entraîner des fuites de mémoire. .

Cela m'inquiète, car j'ai suivi des exemples (à moins que je n'imagine des choses) et que je l'ai fait dans pas mal d'endroits ... Ai-je mal compris? Est-ce le mal? Si oui, à quel point le mal?

Je pense qu'en raison de mon mélange de pointeurs et de références, combiné au fait que je suis un débutant en C++ et à la confusion totale qui entoure l'utilisation de mes applications, mes applications doivent être une fuite de mémoire ...

De plus, je comprends que l’utilisation de pointeurs intelligents/partagés est généralement acceptée comme le meilleur moyen d’éviter les fuites de mémoire.

318
Nick Bolton

En général, renvoyer une référence est parfaitement normal et se produit tout le temps.

Si tu veux dire:

int& getInt() {
    int i;
    return i;  // DON'T DO THIS.
}

C'est toutes sortes de mal. La i attribuée à la pile disparaîtra et vous ne faites référence à rien. C'est aussi le mal:

int& getInt() {
    int* i = new int;
    return *i;  // DON'T DO THIS.
}

Parce que maintenant, le client doit finalement faire l'étrange:

int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt;         // must delete...totally weird and  evil

int oops = getInt(); 
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original

Notez que les références rvalue ne sont encore que des références, de sorte que toutes les applications malveillantes restent les mêmes.

Si vous souhaitez allouer quelque chose qui dépasse le cadre de la fonction, utilisez un pointeur intelligent (ou en général un conteneur):

std::unique_ptr<int> getInt() {
    return std::make_unique<int>(0);
}

Et maintenant, le client stocke un pointeur intelligent:

std::unique_ptr<int> x = getInt();

Les références sont également correctes pour accéder à des éléments où vous savez que la durée de vie est maintenue ouverte à un niveau supérieur, par exemple:

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

Ici, nous savons qu'il est correct de renvoyer une référence à i_, car ce qui nous appelle gère la durée de vie de l'instance de la classe, donc i_ vivra au moins aussi longtemps.

Et bien sûr, il n'y a rien de mal à juste:

int getInt() {
   return 0;
}

Si la durée de vie doit être laissée à l'appelant et que vous ne faites que calculer la valeur.

Résumé: vous pouvez renvoyer une référence si la durée de vie de l'objet ne se termine pas après l'appel.

381
GManNickG

Non, non, mille fois non.

Ce qui est mal, c'est faire référence à un objet alloué dynamiquement et perdre le pointeur d'origine. Lorsque vous new un objet, vous assumez l'obligation de disposer d'une garantie delete.

Mais regardez, par exemple, operator<<: that must retourne une référence, ou

cout << "foo" << "bar" << "bletch" << endl ;

ne fonctionnera pas.

63
Charlie Martin

Vous devez renvoyer une référence à un objet existant qui ne disparaît pas immédiatement et dans lequel vous ne souhaitez pas de transfert de propriété.

Ne retournez jamais une référence à une variable locale ou à une telle, car il ne sera pas possible de la référencer.

Vous pouvez renvoyer une référence à quelque chose d'indépendant de la fonction, ce que vous n'attendez pas de la fonction appelante assumant la responsabilité de la suppression. C'est le cas de la fonction typique operator[].

Si vous créez quelque chose, vous devez renvoyer une valeur ou un pointeur (normal ou intelligent). Vous pouvez renvoyer une valeur librement, car elle entre dans une variable ou une expression de la fonction appelante. Ne jamais renvoyer un pointeur sur une variable locale, car elle disparaîtra.

45
David Thornley

Je trouve que les réponses ne sont pas satisfaisantes, alors je vais ajouter mes deux sous.

Analysons les cas suivants:

Utilisation erronée

int& getInt()
{
    int x = 4;
    return x;
}

C'est évidemment une erreur

int& x = getInt(); // will refer to garbage

Utilisation avec des variables statiques

int& getInt()
{
   static int x = 4;
   return x;
}

C’est vrai, car les variables statiques existent tout au long de la vie d’un programme.

int& x = getInt(); // valid reference, x = 4

Ceci est également assez courant lors de la mise en œuvre du modèle Singleton

Class Singleton
{
    public:
        static Singleton& instance()
        {
            static Singleton instance;
            return instance;
        };

        void printHello()
        {
             printf("Hello");
        };

}

Usage:

 Singleton& my_sing = Singleton::instance(); // Valid Singleton instance
 my_sing.printHello();  // "Hello"

Les opérateurs

Les conteneurs de bibliothèque standard dépendent fortement de l'utilisation des opérateurs qui renvoient une référence, par exemple

T & operator*();

peut être utilisé dans ce qui suit

std::vector<int> x = {1, 2, 3}; // create vector with 3 elements
std::vector<int>::iterator iter = x.begin(); // iterator points to first element (1)
*iter = 2; // modify first element, x = {2, 2, 3} now

Accès rapide aux données internes

Il arrive parfois que & puisse être utilisé pour un accès rapide aux données internes

Class Container
{
    private:
        std::vector<int> m_data;

    public:
        std::vector<int>& data()
        {
             return m_data;
        }
}

avec usage:

Container cont;
cont.data().Push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1

CEPENDANT, cela peut conduire à des pièges tels que:

Container* cont = new Container;
std::vector<int>& cont_data = cont->data();
cont_data.Push_back(1);
delete cont; // This is bad, because we still have a dangling reference to its internal data!
cont_data[0]; // dangling reference!
15
thorhunter

Ce n'est pas mal. Comme beaucoup de choses en C++, il est utile si vous l'utilisez correctement, mais vous devez être conscient de nombreux pièges (comme le renvoi d'une référence à une variable locale).

Il y a de bonnes choses qui peuvent être réalisées avec elle (comme map [name] = "hello world")

14
Mehrdad Afshari

"renvoyer une référence est mauvais parce que, tout simplement [si j'ai bien compris], il est plus facile de ne pas la supprimer"

Pas vrai. Le renvoi d'une référence n'implique pas une sémantique de propriété. C'est simplement parce que vous faites ceci:

Value& v = thing->getTheValue();

... ne signifie pas que vous possédez maintenant la mémoire mentionnée par v;

Cependant, c'est un code horrible:

int& getTheValue()
{
   return *new int;
}

Si vous faites quelque chose comme ceci parce que "vous n'avez pas besoin d'un pointeur sur cette instance" alors: 1) déréférencez simplement le pointeur si vous avez besoin d'une référence, et 2) vous aurez éventuellement besoin du pointeur , car vous devez faire correspondre un nouveau avec un delete et vous avez besoin d’un pointeur pour appeler delete.

10
John Dibling

Il y a deux cas:

  • référence const - bonne idée, parfois, surtout pour les objets lourds ou les classes proxy, optimisation du compilateur

  • référence non-constante - mauvaise idée, parfois, casse l'encapsulation

Les deux partagent le même problème - peuvent potentiellement pointer sur un objet détruit ...

Je recommanderais d'utiliser des pointeurs intelligents dans de nombreuses situations où vous devez renvoyer une référence/un pointeur.

Notez également ce qui suit:

Il y a une règle formelle - la norme C++ (section 13.3.3.1.4 si vous êtes intéressé) stipule qu'un temporaire ne peut être lié qu'à une référence const - si vous essayez d'utiliser une référence non-const au compilateur doit signaler ceci comme une erreur.

7
Sasha

Non seulement ce n'est pas mauvais, mais c'est parfois essentiel. Par exemple, il serait impossible d'implémenter l'opérateur [] de std :: vector sans utiliser une valeur de retour de référence.

4
anon

la référence de retour est généralement utilisée dans la surcharge d'opérateurs en C++ pour les objets volumineux, car le retour d'une valeur nécessite une opération de copie (dans la surcharge perator, nous n'utilisons généralement pas le pointeur comme valeur de retour).

Mais renvoyer référence peut causer des problèmes d'allocation de mémoire. Étant donné qu'une référence au résultat sera transmise à la fonction en tant que référence à la valeur de retour, la valeur de retour ne peut pas être une variable automatique.

si vous voulez utiliser return refernce, vous pouvez utiliser un tampon d'objet statique. par exemple

const max_tmp=5; 
Obj& get_tmp()
{
 static int buf=0;
 static Obj Buf[max_tmp];
  if(buf==max_tmp) buf=0;
  return Buf[buf++];
}
Obj& operator+(const Obj& o1, const Obj& o1)
{
 Obj& res=get_tmp();
 // +operation
  return res;
 }

de cette manière, vous pouvez utiliser le renvoi de référence en toute sécurité.

Mais vous pouvez toujours utiliser un pointeur au lieu d'une référence pour renvoyer une valeur dans functiong.

1
MinandLucy

Ajout à la réponse acceptée:

struct immutableint {
    immutableint(int i) : i_(i) {}

    const int& get() const { return i_; }
private:
    int i_;
};

Je dirais que cet exemple est pas d'accord et devrait être évité si possible. Pourquoi? Il est très facile de se retrouver avec un référence suspendue.

Pour illustrer ce point avec un exemple:

struct Foo
{
    Foo(int i = 42) : boo_(i) {}
    immutableint boo()
    {
        return boo_;
    }  
private:
    immutableint boo_;
};

entrant dans la zone de danger:

Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!
1
AMA

Le mieux est de créer un objet et de le transmettre comme paramètre référence/pointeur à une fonction qui alloue cette variable.

Affecter un objet à une fonction et le renvoyer en tant que référence ou pointeur (le pointeur est plus sûr toutefois) est une mauvaise idée en raison de la libération de mémoire à la fin du bloc de fonction.

0
Drezir

je pense que l'utilisation de référence comme valeur de retour de la fonction est beaucoup plus simple que d'utiliser un pointeur comme valeur de retour de la fonction. Deuxièmement, il serait toujours prudent d'utiliser une variable statique à laquelle la valeur de retour fait référence.

0
Tony