187 votes

Qu'est-ce que auto && nous dit?

Si vous lisez un code comme

auto&& var = foo();

où foo est toute la fonction retourne la valeur de type T. Ensuite, var est une lvalue de type référence rvalue d' T. Mais qu'est-ce que cela implique pour l' var? Signifie-t-il, nous sommes autorisés à voler les ressources de l' var? Sont-il raisonnable des situations où vous devez utiliser auto&& dire le lecteur de votre code quelque chose comme vous le faites lorsque vous retournez un unique_ptr<> pour dire que vous avez la propriété exclusive? Et que dire par exemple T&& lorsque T est de type classe?

Je veux juste comprendre, si il y a d'autres cas d'utilisation de l' auto&& que ceux du modèle de programmation comme discuté dans les exemples dans cet article des Références Universelles de Scott Meyers.

269voto

Joseph Mansfield Points 59346

En utilisant auto&& var = <initializer> vous dire: j'accepte toute initialiseur indépendamment du fait que c'est une lvalue ou rvalue expression et je conserve son constness. Ceci est généralement utilisé pour le transfert (généralement avec T&&). La raison pour laquelle cela fonctionne est parce qu'un "univers de référence", auto&& ou T&&, va se lier à quelque chose.

Vous pourriez dire, eh bien, pourquoi ne pas simplement utiliser un const auto& parce que cela va aussi se lier à quelque chose? Le problème avec l'aide d'un const de référence, c'est que c'est const! Vous ne serez pas en mesure plus tard de se lier à n'importe quel non-const références ou d'appeler l'une des fonctions membres qui ne sont pas marqués const.

Par exemple, imaginez que vous voulez obtenir un std::vector, prendre un itérateur vers son premier élément et modifiez la valeur pointée par l'itérateur d'une certaine façon:

auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;

Ce code permet de compiler des beaux indépendamment de l'initialiseur d'expression. Les alternatives à l' auto&& échouent dans l'une des manières suivantes:

auto         => will copy the vector, but we wanted a reference
auto&        => will only bind to modifiable lvalues
const auto&  => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues

Donc, pour cela, auto&& fonctionne parfaitement! Un exemple d'utilisation de auto&& comme c'est dans une gamme à base for boucle. Voir mon autre question pour plus de détails.

Si vous utilisez ensuite std::forward sur votre auto&& de référence pour préserver le fait qu'il était à l'origine une lvalue ou une rvalue, votre code dit: Maintenant que j'ai votre objet soit à partir d'une lvalue ou rvalue expression, je tiens à préserver selon valueness il avait à l'origine donc je peux l'utiliser le plus efficacement possible - ce qui pourrait l'invalider. Comme dans:

auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));

Cela permet d' use_it_elsewhere pour ripper ses tripes pour des raisons de performance (en évitant les copies) lorsque l'original d'initialiseur était modifiable rvalue.

Qu'est-ce que cela signifie quant à savoir si nous pouvons ou quand on peut voler des ressources d' var? Eh bien, puisque l' auto&& vont se lier à quoi que ce soit, nous ne pouvons pas essayer d'arracher vars tripes en nous - mêmes, elle peut très bien être une lvalue ou même const. Nous pouvons toutefois std::forward à d'autres fonctions qui peuvent totalement ravage de ses entrailles. Dès que nous faisons cela, nous devons considérer var à être dans un état non valide.

Maintenant, nous allons l'appliquer aux cas d' auto&& var = foo();, comme indiqué dans votre question, où foo renvoie une T en valeur. Dans ce cas, nous savons pour sûr que le type d' var sera déduit qu' T&&. Puisque nous savons que pour certains c'est une rvalue, nous n'avons pas besoin std::forward's la permission de voler de ses ressources. Dans ce cas précis, sachant qu' foo rendements en valeur, le lecteur doit simplement le lire: je suis prise de référence rvalue temporaire retourné à partir de foo, de sorte que je peux heureusement déplacer.


Comme un additif, je pense qu'il vaut la peine de mentionner lorsqu'une expression comme some_expression_that_may_be_rvalue_or_lvalue peut tourner jusqu'à, autre qu'une "eh bien, votre code peut changer" de la situation. Voici donc un exemple artificiel:

std::vector<int> global_vec{1, 2, 3, 4};

template <typename T>
T get_vector()
{
  return global_vec;
}

template <typename T>
void foo()
{
  auto&& vec = get_vector<T>();
  auto i = std::begin(vec);
  (*i)++;
  std::cout << vec[0] << std::endl;
}

Ici, get_vector<T>() est cette belle expression qui pourrait être une lvalue ou rvalue selon le type générique T. Nous avons essentiellement changer le type de retour d' get_vector par le paramètre de modèle de foo.

Lorsque nous appelons foo<std::vector<int>>, get_vector sera de retour global_vec en valeur, ce qui donne une rvalue expression. Par ailleurs, lorsque nous appelons foo<std::vector<int>&>, get_vector sera de retour global_vec par référence, résultant en une lvalue expression.

Si nous le faisons:

foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;

Nous obtenons le résultat suivant, comme prévu:

2
1
2
2

Si vous deviez modifier l' auto&& dans le code de l' auto, auto&, const auto&ou const auto&& alors on ne pourra pas obtenir le résultat que nous voulons.


Un autre moyen pour changer la logique du programme basé sur si votre auto&& de référence est initialisé avec une lvalue ou rvalue expression est d'utiliser des traits de type:

if (std::is_lvalue_reference<decltype(var)>::value) {
  // var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
  // var was initialised with an rvalue expression
}

14voto

Xeo Points 69818

Tout d'abord, je recommande la lecture de cette réponse de la mine que d'un côté-lus pour l'étape-par-étape des explications sur comment l'argument de modèle déduction pour des références universelles œuvres.

Signifie-t-il, nous sommes autorisés à voler les ressources de l' var?

Pas nécessairement. Que faire si foo() tout d'un coup retourné une référence, ou si vous avez changé l'appel, mais vous avez oublié de mettre à jour l'utilisation de l' var? Ou si vous êtes dans le code générique et le type de retour d' foo() peut changer en fonction de vos paramètres?

Pensez auto&& à être exactement le même que l' T&& en template<class T> void f(T&& v);, parce que c'est (presque). Que faites-vous avec des références universelles dans les fonctions, lorsque vous avez besoin de les transmettre ou de les utiliser d'une quelconque manière? Vous utilisez std::forward<T>(v) pour obtenir la valeur d'origine catégorie de retour. Si c'était une lvalue avant d'être transférés à votre fonction, il reste une lvalue après être passé par std::forward. Si c'était une rvalue, il deviendra une rvalue de nouveau (rappelez-vous, un nom de référence rvalue est une lvalue).

Alors, comment utilisez-vous var correctement dans un générique de la mode? Utiliser std::forward<decltype(var)>(var). Cela fonctionne exactement le même que l' std::forward<T>(v) dans le modèle de fonction ci-dessus. Si var est T&&, vous obtiendrez une rvalue de retour, et si il est T&, vous obtiendrez une lvalue dos.

Donc, de retour sur le sujet: Que faites - auto&& v = f(); et std::forward<decltype(v)>(v) dans une base de code nous dire? Ils nous disent que, v sera acquis et transmis de la manière la plus efficace. N'oubliez pas, cependant, que, après avoir transmis une telle variable, il est possible qu'il soit déplacé de", de sorte qu'il serait incorrect de l'utiliser sans le remettre.

Personnellement, j'utilise" auto&& dans le générique de code lorsque j'ai besoin d'un modifyable variable. Parfait de transfert d'une rvalue est en train de modifier, depuis l'opération de déplacement potentiellement vole de ses tripes. Si je veux juste être paresseux (c'est à dire, pas orthographier le nom du type, même si je sais que c') et n'ont pas besoin de modifier (par exemple, au moment de l'impression des éléments de la gamme), je vais m'en tenir à l' auto const&.


auto est donc très différente de ce qu' auto v = {1,2,3}; feront v un std::initializer_list, tandis que l' f({1,2,3}) sera une déduction de l'échec.

4voto

Useless Points 18909

Envisager un certain type T qui est un constructeur de déplacement, et d'assumer

T t( foo() );

utilise que constructeur de déplacement.

Maintenant, nous allons utiliser un intermédiaire de référence pour capturer le retour d' foo:

auto const &ref = foo();

ceci exclut l'utilisation du constructeur de déplacement, de sorte que la valeur de retour devra être copié au lieu de déplacer (même si nous utilisons std::move ici, nous ne pouvons pas vraiment se déplacer par l'intermédiaire d'un const ref)

T t(std::move(ref));   // invokes T::T(T const&)

Cependant, si nous utilisons

auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)

le constructeur de déplacement est toujours disponible.


Et pour répondre à vos autres questions:

... Sont-il raisonnable des situations où vous devez utiliser auto&& dire le lecteur de votre code quelque chose ...

La première chose, comme Xeo dit, c'est l'essentiel je suis de passage X de manière aussi efficace que possible, quel que soit le type de X est. Donc, voir le code qui utilise auto&& interne doit communiquer qu'il va utiliser la sémantique de déplacement interne, le cas échéant.

... comme vous le faites lorsque vous retournez un unique_ptr<> pour dire que vous avez la propriété exclusive ...

Lorsqu'un modèle de fonction prend un argument de type T&&, il est dit qu'il peut déplacer l'objet que vous transmettez. De retour unique_ptr donne explicitement la propriété de l'appelant; acceptant T&& peut supprimer la propriété de l'appelant (si un déplacement ctor existe, etc.).

-3voto

reece Points 3115

L' auto && de la syntaxe utilise deux nouvelles fonctionnalités de C++11:

  1. L' auto partie permet au compilateur de déduire le type en fonction du contexte (la valeur de retour dans ce cas). C'est sans aucune référence qualifications (vous permettant de spécifier si vous souhaitez T, T & ou T && pour une déduit de type T).

  2. L' && est la nouvelle sémantique de déplacement. Un type de soutenir la sémantique de déplacement met en œuvre un constructeur T(T && other) que de façon optimale déplace le contenu dans le nouveau type. Cela permet à un objet de swap de la représentation interne au lieu d'effectuer une copie en profondeur.

Cela vous permet d'avoir quelque chose comme:

std::vector<std::string> foo();

Donc:

auto var = foo();

effectue une copie du vecteur renvoyé (cher), mais:

auto &&var = foo();

swap de la représentation interne du vecteur (le vecteur d' foo et le vecteur vide de var), ce sera plus rapide.

Il est utilisé dans la nouvelle boucle for syntaxe:

for (auto &item : foo())
    std::cout << item << std::endl;

Lorsque la boucle for est la tenue d'un auto && de la valeur de retour de foo et item est une référence pour chaque valeur en foo.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X