web-dev-qa-db-fra.com

Renvoie les valeurs de copie des instructions

Je m'interroge à ce sujet en raison de problèmes de portée. Par exemple, considérons le code

typedef struct {
    int x1;/*top*/
    int x2;/*bottom*/
    int id;
} subline_t;



subline_t subline(int x1, int x2, int id) {
    subline_t t = { x1, x2, id };
    return t;
}

int main(){
    subline_t line = subline(0,0,0); //is line garbage or isn't it? the reference
    //to subline_t t goes out of scope, so the only way this wouldn't be garbage
    //is if return copies
}

Ma question est donc la suivante: la déclaration de retour sera-t-elle toujours copiée? Dans ce cas, cela semble fonctionner, alors je suis amené à croire que le retour est une copie. Si c'est le cas, le fera-t-il dans tous les cas?

61
ldog

Oui, dans ce cas, une copie sera faite. Si vous modifiez la déclaration de fonction comme ceci:

subline_t &subline(int x1, int x2, int id) {

alors aucune copie ne sera faite. Toutefois, dans votre cas particulier, il ne serait pas valide de renvoyer une référence à un objet alloué sur la pile. Le problème est que l'objet serait détruit et invalidé avant que l'appelant ne puisse l'utiliser.

Ceci est lié au paramètre commun Optimisation de la valeur de retour pour C++ qui permet d'éviter une opération de copie dans le cas que vous avez décrit. Le résultat final est (ou devrait être) identique à celui d'une copie, mais vous devez être conscient de l'optimisation. La présence de cette optimisation peut, dans certains cas, modifier le comportement observable du programme.

39
Greg Hewgill

Dans votre cas, il retournera une copie

Si votre code était

subline_t& subline(int, int)

alors il renverrait une référence, ce qui donnerait un comportement indéfini.

4
Tom

Oui, pour qu'une fonction déclarée retourne un struct, return d'une telle structure la copie (bien que le compilateur soit habilité à optimiser la copie, essentiellement dans les cas où elle peut prouver que l'optimisation est sémantiquement inoffensive, vous pouvez raisonner "comme si "la copie était garantie).

Cependant, puisque vous avez taggé cela en C++, pas en C, pourquoi ne pas fournir à votre struct un constructeur, au lieu de ...? Cela semble plus clair et plus direct ...! -)

3
Alex Martelli

Il retournera toujours une copie.

Si vous souhaitez éviter les conséquences néfastes de la copie de l'objet lors du retour, vous pouvez déclarer un pointeur, créer une instance de l'objet à l'aide de new et renvoyer le pointeur. Dans ce cas, le pointeur sera copié, mais pas l'objet.

3
csj

oui, le retour est une copie

subline_t subline(int x1, int x2, int id) {
        subline_t t = { x1, x2, id };
        return t;
}

Si vous mettez un référent, alors ce n'est pas une copie

subline_t & subline(int x1, int x2, int id) {
        subline_t t = { x1, x2, id };
        return t; // will result in corruption because returning a reference
}
2
Andrew Keith

Il retourne une copie, ce que vous voulez qu'il fasse. Le changer pour renvoyer une référence entraînera un comportement indéfini dans l'affectation à la ligne.

Cependant, la manière idiomatique de faire cela en C++ est d'utiliser des constructeurs et des listes d'affectation. Cela encapsule mieux le code et les structures de données et vous permet d’éviter la pléthore d’objets intermédiaires que les compilateurs sont libres de construire/détruire/copier.

struct subline_t {
        int x1;/*top*/
        int x2;/*bottom*/
        int id;

// constructor which initialises values with assignment list.
  subline_t(int the_x1, int the_x2, int the_id) :
    x1(the_x1),
    x2(the_x2),
    id(the_id)
  {
  }
};


int main(){
    subline_t line2(0,0,0); // never requires a copy or assignment.
}
1
Roddy

Returing des objets en C++ fait par valeur et non par référence.

la référence à subline_t t sort du cadre

Non, l'objet est copié.

la déclaration de retour sera-t-elle toujours copiée?

Oui et non ... Sémantiquement, il se comporte comme une copie, mais il existe quelque chose appelé optimisation de la valeur de retour qui enregistre le constructeur de copie.

foo make_foo()
{
    foo f(1,2,3);
    return f;
}

foo ff=make_foo(); /// ff created as if it was created with ff(1,2,3) -- RVO
foo ff2;
ff2=make_foo(); /// instance of foo created and then copied to ff2 and then old
                /// instance destroyed
1
Artyom

La classe ou la structure renvoyée peut ou non être copiée, selon que le compilateur utilise ou non la copie. Voir les réponses à Que sont l’élision de la copie et l’optimisation de la valeur de retour? En bref, qu’il soit copié ou non dépend d’un certain nombre de choses.

Vous pouvez bien sûr éviter une copie en renvoyant une référence. Dans le cas de votre exemple, le renvoi d'une référence n'est pas valide (bien que le compilateur le permette), car la structure locale est allouée sur la pile et la référence renvoyée fait donc référence à un objet désalloué. Toutefois, si l'objet a été transmis à votre fonction (directement ou en tant que membre d'un objet), vous pouvez lui renvoyer en toute sécurité une référence et éviter la copie avec retour.

Enfin, si vous ne pouvez pas faire confiance à la copie et que vous voulez éviter les copies, vous pouvez utiliser et renvoyer un unique_ptr au lieu d’une référence. L'objet lui-même ne sera pas copié, bien que le unique_ptr lui-même puisse ne pas l'être (encore une fois, en fonction de la décision de copie!). Copier/déplacer un unique_ptr est toutefois très économique si la copie du unique_ptr ne se produit pas pour une raison quelconque.

Voici un exemple utilisant unique_ptr:

#include <memory>

struct A {
public:
  int x;
  int y;

  A(int x, int y) : x(x), y(y) {
  }
};

std::unique_ptr<A> returnsA() {
  return std::make_unique<A>(3, 4);
}

int main() {
  auto a = returnsA();
}

Notez que vous devez (malheureusement) déclarer un constructeur pour votre structure, sinon make_unique ne compilera pas à cause d'insuffisances de C++.

0
Alex K.

Pour la structure subline_t que vous avez définie, oui, elle renverra toujours une copie.

0
Jacob