2016-11-21 3 views
7

Prenons l'exemple minimale suivante:std :: list et std :: for_each: où est ma fin?

#include <functional> 
#include <algorithm> 
#include <list> 

int main() { 
    std::list<std::function<void()>> list; 
    list.push_back([&list](){ list.push_back([](){ throw; }); }); 
    std::for_each(list.cbegin(), list.cend(), [](auto &&f) { f(); }); 
} 

Il compile et lance une exception au moment de l'exécution. Je suppose que seul le premier lambda est exécuté par le std::for_each, mais apparemment je me trompais: si j'ajoute un autre lambda à la fin de la liste, l'itération atteint aussi lambda.

Nous allons nous revenions l'exemple (push_front au lieu de push_back et crbegin/crend au lieu de cbegin/cend):

#include <functional> 
#include <algorithm> 
#include <list> 

int main() { 
    std::list<std::function<void()>> list; 
    list.push_front([&list](){ list.push_front([](){ throw; }); }); 
    std::for_each(list.crbegin(), list.crend(), [](auto &&f) { f(); }); 
} 

En raison de l'exemple précédent, je me attendais à ce compiler et accident ainsi.
Au lieu de cela, il compile et ne plante pas. Cette fois, la fonction mise en avant de la liste n'est pas exécutée.

La question est assez simple: est-ce correct?
Pourquoi sont si contre-intuitifs les deux exemples?

Dans le premier cas, je m'attendais à quelque chose de différent et j'avais tort, ce n'est pas un problème.
Quoi qu'il en soit, j'aurais attendu une cohérence entre les deux boucles. Je veux dire, la deuxième fonction est exécutée dans un cas et elle n'est pas exécutée dans l'autre cas, mais je répète d'un commencer à un fin dans les deux cas.
Qu'est-ce qui ne va pas dans mon raisonnement?

+1

compilateur utilisé? –

+0

@GillBates GCC 6.1 et 3.9 fonctionnent comme décrit dans la question. Est-ce pertinent? Je ne pensais pas que cela pourrait être un problème du compilateur. – skypjack

+3

Ne devrait-il pas ajouter en effet le lambda (premier exemple)? Les itérateurs ne sont pas invalidés comme dans le cas de 'std :: vector'. – vsoftco

Répondre

6

Pour être honnête, les résultats que vous obtenez semblent être ce que j'attendais. Parcourons vous premier exemple:

1.

list.push_back([&list](){ list.push_back([](){ throw; }); }); 

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
v 
[lambda]----[end] 

2.commencer à itérer sur la liste

Itération 1:

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
v 
[lambda]----[end] 
^ 
+-- current 

f() appels list.push_back([](){ throw; });

Liste État:

+-- list.begin() (not necessarily what has been passed to for_each) 
v 
[lambda]----[inner_lambda]----[end] 
^ 
+-- current 

Itération 2: (++current)

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
v 
[lambda]----[inner_lambda]----[end] 
      ^
      +-- current 

f() appelle throw;

fin



Maintenant, nous allons faire l'autre sens.

Tout d'abord, regardez comment inverse itérateurs sont effectivement représentés - ce qui est important (l'image de cppreference): reverse iterators

La partie importante est: inverse des points d'extrémité à la normale commencer. Mais le problème est, avec une liste, un peut insérer quelque chose avant begin, mais ce n'est pas possible après end. Cet invariant est brisé avec des itérateurs inverses.

1.

list.push_front([&list](){ list.push_front([](){ throw; }); }); 

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
| 
|+-- list.rend().base() 
|| 
||   +-- list.rbegin().base() 
vv   v 
[lambda]----[end] 

2. commencer à itérer sur la liste

Iteration 1:

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
| 
|+-- list.rend().base() 
|| 
||   +-- list.rbegin().base() 
vv   v 
[lambda]----[end] 
    ^  ^
    |   +---- current 
    | 
    +--------- passed list.rend() 

*current cède [lambda].

f() appelle list.push_front([](){ throw; });

état Liste:

+-- list.begin() (not necessarily what has been passed to for_each) 
| 
|+-- list.rend().base() 
|| 
||       +-- list.rbegin().base() 
vv       v 
[inner_lambda]----[lambda]----[end] 
        ^  ^
         |   +---- current 
         | 
         +--------- passed list.rend().base() 

Notez que le passé list.rend().base() n'a pas changé - mais il ne pointe pas vers le premier (après le dernier inversé) plus l'élément.

Itération 2: (++current)

+-- list.begin() (not necessarily what has been passed to for_each) 
| 
|+-- list.rend().base() 
|| 
||       +-- list.rbegin().base() 
vv       v 
[inner_lambda]----[lambda]----[end] 
        ^^ 
         | +---- current 
         | 
         +--------- passed list.rend().base() 

current == passed list.rend().base()

la fin



Maintenant, nous allons essayer l'autre par mon erreur cette partie est pertinent pour forward iterating ov er la liste:

1.

list.push_front([&list](){ list.push_front([](){ throw; }); }); 

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
v 
[lambda]----[end] 

2. commencer à itérer sur la liste

Itération 1:

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
v 
[lambda]----[end] 
^ 
+-- current 

f() appels list.push_front([](){ throw; });

Iterator à courant n'est pas invalidée et/ou fait au point ailleurs que déjà pointait.

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
v 
[inner_lambda]----[lambda]----[end] 
       ^
        +-- current 

Iteration 2: (++current)

Liste état:

+-- list.begin() (not necessarily what has been passed to for_each) 
v 
[inner_lambda]----[lambda]----[end] 
          ^
           +-- current 

fin

+0

Il ne tient pas compte du fait que les itérateurs réels _begin_ et _end_ réels sont copiés, donc fixés dans 'std :: for_each'. Dans l'exemple, vous ne considérez que _begin_ et _current_. Mon doute est sur le fait que _end_ (laissez-moi dire) _changes_. – skypjack

+1

@skypjack * end * ne change pas, et courant est le début que vous avez réussi. Mais votre lambda fonctionne sur toute la liste, pas sur le sous-ensemble que vous avez passé. Par conséquent, dans le deuxième exemple, il ajoute un élément avant que votre courant ne commence. – krzaq

+0

Je vois que vous pointez. C'est un peu contre-intuitif pour moi, mais cela ne veut pas dire que c'est en réalité un "wrong". Laissez-moi réfléchir à deux fois à ce sujet. – skypjack