2016-11-21 47 views
7

Rozważmy następującą minimalną przykład:std :: list i std :: for_each: gdzie jest mój koniec?

#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(); }); 
} 

to kompiluje i zgłasza wyjątek, w okresie czasu.
Domyślam się, że tylko pierwsza lambda jest wykonywany przez std::for_each, ale widocznie się myliłem: jeśli dołączy kolejny lambda na końcu listy, iteracja dotrze również, że lambda.

Załóżmy przywrócić przykład (push_front zamiast push_back i crbegin/crend zamiast 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(); }); 
} 

Ponieważ w poprzednim przykładzie, spodziewałem się tego, aby skompilować i awarii, jak również.
Zamiast tego kompiluje się i nie ulega awarii. Tym razem funkcja przesunięta na początek listy nie zostanie wykonana.

pytanie jest dość prosta: jest to prawidłowe?
Dlaczego dwa przykłady są sprzeczne z intuicją?

W pierwszym przypadku Spodziewałem się czegoś innego i nie miałem racji, że nie jest to problem.
W każdym razie oczekiwałbym spójności między dwiema pętlami. To znaczy, druga funkcja jest wykonywana w jednym przypadku i nie jest wykonywana w drugim przypadku, ale ja iteracji z rozpocząć do koniec w obu przypadkach.
Co jest nie tak w moim rozumowaniu?

+1

Compiler używane? –

+0

@GillBates Zarówno GCC 6.1 i clang 3.9 działają zgodnie z opisem w pytaniu. Czy to istotne? Nie sądziłem, że może to być problem kompilatora. – skypjack

+3

Czy nie powinien on faktycznie dodać lambda (pierwszy przykład)? Iteratory nie są unieważniane jak w przypadku 'std :: vector'. – vsoftco

Odpowiedz

6

Szczerze mówiąc, wyniki można uzyskać wydaje się być to, co się spodziewałem. Przejdźmy przez ciebie pierwszy przykład: stan

1.

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

Lista:

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

2.rozpocząć iteracji po liście

Iteracja 1: stan

Lista:

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

f() połączeń państwowe list.push_back([](){ throw; });

Lista:

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

Iteracja 2: (++current)

stan

Lista:

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

f() wzywa throw;

koniec



Teraz zróbmy innym kierunku.

Przede wszystkim patrzeć jak odwrotna iteratory są właściwie reprezentowane - to jest ważne (obraz z cppreference): reverse iterators

Ważną częścią jest: reverse punktów końcowych, aby rozpocząć normalne. Problem polega jednak na tym, że z jedną z nich można wstawić coś przed begin, ale nie jest to możliwe po end. Ten niezmiennik jest zepsuty przez odwrotne iteratory. stan

1.

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

Lista:

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

2. rozpocząć iteracji po liście

Iteracja 1: stan

listy:

+-- 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 daje [lambda].

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

stan Lista:

+-- 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() 

Zauważ, że przeszedł list.rend().base() nie zmieniła - ale to nie wskazuje na pierwszy (za ostatnim elementem już odwrócone).

Iteracja 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()

koniec



Teraz spróbujmy ten drugi przez moją pomyłkę ta część istotne dla forward iterującego ov er lista: stan

1.

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

Lista:

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

2. rozpocząć iteracji po liście

Iteracja 1: stan

Lista:

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

f() połączeń list.push_front([](){ throw; });

Iterator aby prąd nie podważa i/lub wprowadzone do punktu gdzie indziej niż dotychczas wskazywał. stan

Lista:

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

Iteracja 2: (++current) stan

Lista:

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

koniec

+0

Nie bierze pod uwagę faktu, że faktyczne _begin_ i rzeczywiste _end_ iteratory są kopiowane, a więc poprawione w 'std :: for_each'. W tym przykładzie rozważasz tylko _begin_ i _current_. Moje wątpliwości dotyczą tego, że _end_ (pozwól mi powiedzieć) _changes_. – skypjack

+1

@skypjack * koniec * nie zmienia się, a bieżący jest początkiem, który minął. Ale twoja lambda działa na całej liście, a nie na podzbiorze, który minąłeś. Dlatego w drugim przykładzie dodaje element przed twoim bieżącym rozpoczęciem. – krzaq

+0

Widzę, że punkt. Jest to dla mnie trochę sprzeczne z intuicją, ale nie oznacza to, że jest ono faktycznie niewłaściwe. Pozwól mi pomyśleć dwa razy. – skypjack

Powiązane problemy