339 votes

Pourquoi le lambda de C ++ 0x nécessite-t-il un mot clé "mutable" pour la capture par valeur, par défaut?

Petit 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 l' 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, n'est-ce pas?

Tout enlightenments?

(Je suis en utilisant MSVC2010 par la voie. Autant que je sache, ce devrait être la norme)

279voto

Puppy Points 90818

Il nécessite mutable car, par défaut, un objet fonction doit produire le même résultat à chaque fois qu'il est appelé. C'est la différence entre une fonction orientée objet et une fonction utilisant une variable globale, de manière efficace.

143voto

Daniel Muñoz Points 302

Votre code est presque équivalent à ceci:

 #include <iostream>

class unnamed1
{
    int& n;
public:
    unnamed1(int& N) : n(N) {}

    /* OK. Your this is const but you don't modify the "n" reference,
    but the value pointed by it. You wouldn't be able to modify a reference
    anyway even if your operator() was mutable. When you assign a reference
    it will always point to the same var.
    */
    void operator()() const {n = 10;}
};

class unnamed2
{
    int n;
public:
    unnamed2(int N) : n(N) {}

    /* OK. Your this pointer is not const (since your operator() is "mutable" instead of const).
    So you can modify the "n" member. */
    void operator()() {n = 20;}
};

class unnamed3
{
    int n;
public:
    unnamed3(int N) : n(N) {}

    /* BAD. Your this is const so you can't modify the "n" member. */
    void operator()() const {n = 10;}
};

int main()
{
    int n;
    unnamed1 u1(n); u1();    // OK
    unnamed2 u2(n); u2();    // OK
    //unnamed3 u3(n); u3();  // Error
    std::cout << n << "\n";  // "10"
}
 

Donc, vous pourriez penser à lambdas comme générant une classe avec operator () par défaut, à moins que vous ne disiez qu’elle est mutable.

Vous pouvez également penser à toutes les variables capturées à l'intérieur de [] (explicitement ou implicitement) en tant que membres de cette classe: copies des objets pour [=] ou références aux objets pour [&]. Ils sont initialisés lorsque vous déclarez votre lambda comme s’il y avait un constructeur caché.

42voto

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, n'est-ce pas?

La question est, est-ce "presque"? Une utilisation fréquente de cas semble être de retour ou de passer des lambdas:

void registerCallback(std::function<void()> f) { /* ... */ }

void doSomething() {
  std::string name = receiveName();
  registerCallback([name]{ /* do something with name */ });
}

Je pense qu' 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.

41voto

akim Points 1404

FWIW, Herb Sutter, un membre bien connu du C++ comité de normalisation, fournit une réponse différente à cette question en Lambda de l'Exactitude et de la Convivialité Questions:

Considérer cet homme de paille exemple, où le programmeur capte une variable locale par valeur et tente de modifier l' capturé valeur (qui est une variable de membre de la lambda objet):

int val = 0;
auto x = [=](item e)            // look ma, [=] means explicit copy
            { use(e,++val); };  // error: count is const, need ‘mutable'
auto y = [val](item e)          // darnit, I really can't get more explicit
            { use(e,++val); };  // same error: count is const, need ‘mutable'

Cette fonctionnalité semble avoir été ajouté le souci de l'utilisateur pourriez ne pas réaliser qu'il a obtenu une copie, et en particulier depuis les lambdas sont copiable, il pourrait être la modification d'une lambda de la copie.

Son livre est 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.

17voto

Xeo Points 69818

Voir à ce projet, sous 5.1.2 [expr.prim.lambda], le sous-alinéa 5:

La fermeture pour un type lambda-expression a un public inline appel de fonction de l'exploitant (13.5.4) dont les paramètres et le type de retour sont décrites par le lambda-expression du paramètre-déclaration de la clause et trailingreturn- type respectivement. Cet appel de fonction de l'opérateur est déclarée const (9.3.1) si et seulement si le lambdaexpression de l' paramètre déclaration de la clause n'est pas suivie par mutable.

Edit 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 tout bon.

Edit sur kizzx2 commentaire: La plupart du temps quand une lambda est utilisée comme un foncteur pour les algorithmes. La valeur par défaut constness lui permet d'être utilisé dans un environnement constant, tout comme normale const-qualifiés, les fonctions peuvent être utilisées, mais non-const-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. :)

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