Pourquoi est-ce que C++11 lambda besoin “mutable” mot-clé pour la capture par valeur, par défaut?
Court exemple:
#include <iostream>
int main()
{
int n;
[&](){n = 10;}(); //OK
[=]() mutable {n = 20;}(); //OK
//[=](){n = 10;}(); //Error: a by-value capture cannot be modified in a non-mutable lambda
std::cout << n << "\n"; //"10"
}
La question: Pourquoi avons-nous besoin de la mutable
mot-clé? Il est tout à fait différent du traditionnel passage de paramètres à des fonctions nommées. Quelle est la logique derrière?
J'étais sous l'impression que le point de l'ensemble de la capture-par-valeur est de permettre à l'utilisateur de modifier le temporaire -- sinon, je suis presque toujours préférable d'utiliser la capture par référence, ne suis-je pas?
Tout enlightenments?
(Je suis en utilisant MSVC2010 par la voie. Autant que je sache, ce devrait être la norme)
- Bonne question, bien que je suis content que quelque chose est enfin
const
par défaut! - Pas une réponse, mais je pense que c'est une chose sensée: si vous prenez quelque chose en valeur, vous ne devriez pas être en train de changer juste pour vous sauver 1 copie d'une variable locale. Au moins vous ne faites pas l'erreur de changement de n en remplaçant = avec &.
- Je suis absolument d'accord avec stefaanv. La raison en est probablement qu'ils entendaient la langue pour être plus simple à apprendre. Un débutant pourrait essayer de mettre en œuvre une fonction d'échange lambda avec la valeur de la capture. C'est ce qui est communément appelé le principe de moindre surprise.
- Pas sûr que c'est bon, lorsque tout le reste n'est pas
const
par défaut. - Szelei: ne Pas commencer un argument, mais à mon humble avis, le concept de "facile à apprendre" n'a pas sa place dans le langage C++, en particulier dans les temps modernes. De Toute Façon 😛
- Bjarne lui-même l'a affirmé dans parler il y a deux ans. Je ne dis pas que C++ est facile à apprendre (ni celui de C++0x est), mais ce et les références rvalue les deux semblent viser le moins surprise de principe.
- Szelei, juste par curiosité, comment est-référence rvalue "le moins surprenant"?
- Parce que l'utilisation de références rvalue vous pouvez transmettre des valeurs temporaires à des fonctions qui s'attendent références (qui est, les références rvalue). Avec seulement lvalue refs vous ne pouvez pas. Cela, bien sûr, repose sur la personne qui a écrit cette fonction en particulier (et à être sensible, ils écrivent une avec deux r et lvalue de surcharge). Une meilleure façon de phrase, c'est que les références rvalue autoriser les responsables de l'implémentation de l'API pour écrire moins surprenant, le code de l'API de l'utilisateur (c'est pourquoi je dis qu'il vise pour cela). Au moins c'est ma compréhension de la motivation à l'origine de ces choses.
- "le point de l'ensemble de la capture-par-valeur est de permettre à l'utilisateur de modifier le temporaire" - Non, l'essentiel est que le lambda peut rester valable au-delà de la durée de vie de tout capturé variables. Si C++ lambdas seulement eu de la capture-par-ref, ils seraient inutilisables dans beaucoup trop de scénarios.
Vous devez vous connecter pour publier un commentaire.
Il exige
mutable
parce que, par défaut, un objet de fonction devrait produire le même résultat à chaque fois qu'il appelle. C'est la différence entre un objet orienté en fonction et une fonction en utilisant une variable globale, efficace.void f(const std::function<int(int)> g)
. Comment suis-je assuré queg
est en fait referentially transparent?g
s'fournisseur pourrait avoir utilisémutable
de toute façon. Donc je ne sais pas. D'autre part, si la valeur par défaut est non-const
, et les gens doivent ajouterconst
au lieu demutable
à des objets de fonction, le compilateur peut respecter laconst std::function<int(int)>
partie et maintenantf
peut supposer queg
estconst
, non?int i = 1; auto f = [i](){++i;}; f(); f(); f(); f();
Si ce n'est pas illégal, l'appel de quatre fois f() produira le même résultat, non? À MOINS que, lei
dans le lambda et l'extérieuri
sont le même objet. Donc, la question devient: est-ce la capture d'objet lambda et l'objet extérieur sont le même objet lors de la capture par valeur? Eh bien, je pense que c'PAS parce que c'est de la capture-par-valeur, au lieu de capture par référence. Alors, pourquoi ne pouvons-nous changer la capture de valeur lorsque nous utilisons la méthode de capture-par-valeur? Je pense que la réponse de Daniel, c'est mieux.[=]() mutable {++n;}();
, commen=20
en fait de produire le même résultat à chaque fois, ce qui fait de la nécessité demutable
moins évident. Notez également le commentaire de @ZsoltSzatmari préciser plus avant.Votre code est presque équivalent à ceci:
De sorte que vous pourriez penser lambdas comme la génération d'une classe avec l'opérateur (), qui est par défaut à const sauf si vous dites que c'est mutable.
Vous pouvez aussi penser à toutes les variables capturées à l'intérieur de [] (explicitement ou implicitement) en tant que membres de cette classe: des copies des objets pour [=] ou références aux objets pour [&]. Ils sont initialisés lorsque vous déclarez vos lambda comme si il y avait un caché constructeur.
const
oumutable
lambda pourrait ressembler si elles sont appliquées comme équivalent types définis par l'utilisateur, la question est (comme dans le titre et élaborée par les OP dans les commentaires) pourquoiconst
est la valeur par défaut, donc ce n'est pas la réponse.La question est, est-ce "presque"? Une utilisation fréquente de cas semble être de retour ou de passer des lambdas:
Je pense que
mutable
n'est pas un cas de "presque". Je considère que "la capture par la valeur de" j'aime "de me permettre d'utiliser sa valeur après la capture d'entité meurt" plutôt que "permettez-moi de changer une copie de celui-ci". Mais peut-être que cela peut faire valoir.const
? Quel but est-il atteint?mutable
semble hors de propos ici, quandconst
est pas la valeur par défaut dans "presque" (:P) tout le reste de la langue.const
a la valeur par défaut, au moins les gens seront contraints de considérer const-correctness :/const
donc ils pourraient l'appeler si oui ou non le lambda objet const. Par exemple, ils pourraient passer à une fonction prenant unstd::function<void()> const&
. Pour permettre le lambda changer sa capturé des copies, dans les premières études les données membres de la fermeture ont été définismutable
en interne automatiquement. Maintenant que vous avez à mettre manuellementmutable
dans l'expression lambda. Je n'ai pas trouvé une justification détaillée des si.const
"Proposition" dans le papier a chuté d'une façon. C'est en fait la façon dont je m'y attendais (en ligne avec le reste de la langue, comme unconst
fonction membre) (comme les programmeurs en C++, cela ne nous dérange pas d'être taxé de typeconst
-- c'est un fait de la vie). Maismutable
semble avoir émergé à la fin pour une raison quelconque.FWIW, Herb Sutter, un membre bien connu du C++ comité de normalisation, fournit une réponse différente à cette question dans la Lambda Exactitude et de Problèmes d'ergonomie:
Son rapport sur les raisons de ce qui devrait être changé en C++14. Il est court, bien écrit, la lecture vaut la peine si vous voulez savoir "ce qui est sur [membre du comité] esprit" en ce qui concerne cette fonctionnalité particulière.
Voir ce projet, sous 5.1.2 [expr.prim.lambda], le sous-alinéa 5:
Modifier sur litb commentaire:
Peut-être qu'ils ont pensé de la capture par valeur, de sorte qu'en dehors des changements de variables ne sont pas reflétés à l'intérieur de la lambda? Références fonctionner dans les deux sens, de sorte que c'est mon explication. Ne sais pas si c'est une bonne cependant.
Modifier sur kizzx2 commentaire:
La plupart du temps quand une lambda est utilisée comme un foncteur pour les algorithmes. La valeur par défaut
const
ness lui permet d'être utilisé dans un environnement constant, tout comme normaleconst
qualifiés fonctions peuvent être utilisées là, mais nonconst
qualifiés peuvent pas. Peut-être ils ont juste pensé à le rendre plus intuitif pour ces cas, et qui sait ce qui se passe dans leur esprit. 🙂const
par défaut? J'ai déjà eu une nouvelle copie, il semble étrange de ne pas le laisser me changer, surtout qu'il n'est pas quelque chose principalement de mal à ça -- ils veulent m'ajoutermutable
.var
en tant que mot clé pour permettre le changement et la constante étant la valeur par défaut pour tout le reste. Maintenant, nous ne le faisons pas, alors nous avons à vivre avec ça. OMI, C++2011 est sorti assez bien, compte tenu de tout.Vous devez penser quel est le fermeture type de votre fonction Lambda. Chaque fois que vous déclarez une expression Lambda, le compilateur crée une fermeture du type, qui n'est rien moins qu'une nouvelle déclaration de classe avec des attributs (environnement où l'expression Lambda où déclarés) et l'appel de la fonction
::operator()
mis en œuvre. Lorsque vous capturez une variable à l'aide de copie par valeur, le compilateur va créer un nouveauconst
attribut dans le type de fermeture, de sorte que vous ne pouvez pas la modifier à l'intérieur de l'expression Lambda parce que c'est une "lecture seule" attribut, c'est la raison pour laquelle ils appellent ça une "fermeture", parce que d'une certaine façon, vous fermez votre expression Lambda en copiant les variables de la haute portée dans le Lambda de la portée. Lorsque vous utilisez le mot-clémutable
, la capture d'entité est devenu unnon-const
attribut de votre type de fermeture. C'est ce qui provoque les changements fait dans la mutable variable capturée par valeur, pour ne pas être propagées à haut champ, mais garder l'intérieur de la dynamique Lambda.Toujours essayer d'imaginer la fermeture consécutive de type de votre expression Lambda, qui m'a beaucoup aidé, et j'espère que cela peut vous aider aussi.
n
est pas temporaire. n est un membre de la lambda-la fonction de l'objet que vous créez avec l'expression lambda. L'attente par défaut est que l'appel de votre lambda ne pas modifier son état, il est donc const pour vous empêcher de modifier accidentellementn
.Il y a maintenant une proposition visant à alléger la nécessité
mutable
en lambda déclarations: n3424mutable
est même un mot-clé en C++.Vous devez comprendre ce que la capture de moyens! elle capture pas d'argument en passant! regardons quelques exemples de code:
Comme vous pouvez le voir, même si
x
a été modifié pour20
le lambda est encore de retour 10 (x
est encore5
à l'intérieur de la lambda)Changer
x
à l'intérieur de la lambda moyen de changer le lambda lui-même à chaque appel (le lambda est la mutation à chaque appel). Pour faire respecter l'exactitude de la norme introduit lamutable
mot-clé. En spécifiant un lambda comme mutable vous dites que chaque appel à la lambda pourrait entraîner un changement dans la lambda lui-même. Voyons un autre exemple:L'exemple ci-dessus montre qu'en faisant le lambda mutable, l'évolution
x
à l'intérieur de la lambda "mute" la lambda à chaque appel avec une nouvelle valeur dex
qui n'a pas de chose à voir avec la valeur réelle dex
dans la fonction principaleD'étendre Chiot réponse, lambda fonctions sont conçues pour être les fonctions pures. Cela signifie que chaque appel donné un unique jeu de données d'entrée retourne toujours le même résultat. Nous allons définir entrée comme l'ensemble de tous les arguments plus toutes les variables lorsque le lambda est appelé.
Dans les fonctions pures de sortie ne dépend que de l'entrée et non pas sur certaines internes de l'état. Par conséquent, toute fonction lambda, si pure, n'a pas besoin de changer son état et est donc immuable.
Lorsqu'un lambda capture par référence, l'écriture sur capturés variables est une contrainte sur le concept de fonction pure, parce que toute une pure fonction est de permettre le retour d'une sortie, même si le lambda n'est certainement pas muter, car l'écriture arrive à des variables externes. Même dans ce cas, une utilisation correcte implique que si le lambda est appelée avec la même entrée, la sortie sera la même à chaque fois, en dépit de ces effets secondaires sur par-ref variables. Ces effets secondaires sont juste des façons de revenir à l'entrée supplémentaire (par exemple, mise à jour d'un compteur) et pourraient être reformulés dans une fonction pure, par exemple le retour d'un n-uplet au lieu d'une valeur unique.