2017-08-09 4 views
1

Conceptuellement et pratique, quelle est la bonne façon de retourner la valeur modifiée d'une fonction que potentiellement prend une référence de valeur r.Quel est le retour correct dans une fonction qui modifie une référence de valeur r potentielle?

template<class Vector> 
??? add_element(Vector&& v){ 
    v.emplace_back(1.); 
    return ???(v); 
} 

Intuition me dit cela, (parce que je ne lâche l'information du type d'origine)

template<class Vector> 
Vector&& add_element(Vector&& v){ 
    v.emplace_back(1.); 
    return std::forward<Vector>(v); 
} 

mais parmi d'autres possibilités est

template<class Vector> 
Vector& add_element(Vector&& v){ 
    v.emplace_back(1.); 
    return v; 
} 

ou même ceux-ci, (basé sur ce https://pizer.wordpress.com/2009/04/13/c0x-do-people-understand-rvalue-references/)

template<class Vector> 
Vector // or typename std::decay<Vector>::type 
add_element(Vector&& v){ 
    v.emplace_back(1.); 
    return v; // or std::forward<Vector>(v); 
} 

Quelle est la manière la plus générique de retourner l'argument passé modifié?

Répondre

5

Dans une situation plus générale avec les références de transfert, la façon dont vous revenez dépend de ce que vous voulez faire. Heureusement, dans ce cas, tous les choix sauf un sont erronés (dangereux, fragiles, etc.), ce qui rend la tâche facile, car vous retournez un conteneur.

template<class Vector> 
Vector add_element(Vector&& v){ 
    v.emplace_back(1.); 
    return std::forward<Vector>(v); 
} 

C'est ce que je fais habituellement. Cela peut prendre un caractère temporaire et, si c'est le cas, déplacer le temporaire dans la valeur de retour.

S'il prend un caractère non temporaire, il le modifie et renvoie une référence de même catégorie.

template<class Vector> 
Vector&& add_element(Vector&& v){ 
    v.emplace_back(1.); 
    return std::forward<Vector>(v); 
} 

Ceci est malheureusement une mauvaise idée. L'extension de durée de vie de référence ne commute pas sur les appels de fonction, donc

auto&& v = add_element(make_vector()); 

entraîne une fuite. En supposant que vos types sont peu coûteux à déplacer (comme un vecteur), renvoyer une copie temporaire vers une copie temporaire est un coût trivial qui permet l'extension de durée de vie de référence. "Mais", vous dites, "Je n'utilise pas auto&&". Eh bien, pour les conteneurs, for(:) boucles faire, et la référence se balance. Donc, en règle générale, ne retourne jamais un conteneur par référence rvalue. Cela à la fois perd des informations de type et empêche l'extension de durée de vie de référence.

Ne jamais rendre un temporaire par & si vous pouvez l'aider.

Ce comportement étrange lorsqu'il est passé une lvalue:

template<class Vector> 
std::decay_t<Vector> add_element(Vector&& v){ 
    v.emplace_back(1.); 
    return std::forward<Vector>(v); 
} 

comme les deux copies et modifie son argument. Je ne sais pas pourquoi vous voulez faire ça.

+0

Si je comprends bien, même si l'argument n'est pas un conteneur, la première option est le cas qui donne la performance sans compromis et la généralité. non? – alfC

+0

@alfC Si vous avez des objets coûteux à déplacer, il est clair que ce n'est pas une performance sans compromis. Tous les conteneurs 'std' mutables en longueur sont peu coûteux à copier, ce qui ne s'applique pas ici. – Yakk

+0

également "ne jamais retourner un conteneur par référence rvalue.": Comme contre-exemple, 'std :: optionnel :: operator *()' ou 'std :: optionnel :: value()' _do_ renvoie par référence rvalue. –