Krótki przykład:
#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"
}
Pytanie: Dlaczego potrzebujemy mutable
słowa kluczowego? Różni się to od tradycyjnego przekazywania parametrów do nazwanych funkcji. Jakie jest uzasadnienie?
Miałem wrażenie, że celem przechwytywania według wartości jest umożliwienie użytkownikowi zmiany ustawienia tymczasowego - w przeciwnym razie prawie zawsze lepiej jest używać przechwytywania przez odniesienie, prawda?
Jakieś oświecenia?
(Nawiasem mówiąc, używam MSVC2010. AFAIK to powinno być standardowe)
const
domyślnie!const
jest domyślnie.Odpowiedzi:
Wymaga to,
mutable
ponieważ domyślnie obiekt funkcyjny powinien generować ten sam wynik przy każdym wywołaniu. Jest to różnica między funkcją zorientowaną obiektowo a funkcją efektywnie wykorzystującą zmienną globalną.źródło
void f(const std::function<int(int)> g)
. Jestem . W jaki sposób mam zagwarantowane, żeg
jest to tak naprawdę przejrzyste odniesienie ?g
dostawca mógł imutable
tak skorzystać . Więc nie będę wiedział. Z drugiej strony, jeśli domyślny jest nie-const
, a ludzie muszą dodaćconst
zamiastmutable
do obiektów funkcyjnych, kompilator może faktycznie egzekwowaćconst std::function<int(int)>
część i terazf
można zakładać, żeg
jestconst
, nie?Twój kod jest prawie równoważny z tym:
Możesz więc pomyśleć o lambdach jako o generowaniu klasy z operatorem (), która domyślnie przyjmuje wartość const, chyba że powiesz, że jest zmienna.
Możesz również myśleć o wszystkich zmiennych przechwyconych wewnątrz [] (jawnie lub niejawnie) jako członkach tej klasy: kopie obiektów dla [=] lub odniesienia do obiektów dla [&]. Są inicjowane, kiedy deklarujesz swoją lambda, jakby istniał ukryty konstruktor.
źródło
const
lubmutable
lambda, gdyby zostały zaimplementowane jako równoważne typy zdefiniowane przez użytkownika, pytanie brzmi (jak w tytule i opracowane przez OP w komentarzach), dlaczegoconst
jest domyślne, więc nie odpowiada na to pytanie.Pytanie brzmi, czy to „prawie”? Częstym przypadkiem użycia wydaje się być zwrot lub przekazanie lambdas:
Myślę, że
mutable
to nie jest przypadek „prawie”. Uważam, że „przechwytywanie według wartości” jak „pozwala mi używać jego wartości po śmierci przechwyconego bytu”, a nie „zezwalam na zmianę jego kopii”. Ale może to można argumentować.źródło
const
? Jaki cel to osiąga?mutable
wydaje się nie na miejscu, gdy nieconst
jest domyślnym w „prawie” (: P) całej reszcie języka.const
było to ustawienie domyślne, przynajmniej ludzie byliby zmuszeni rozważyć stałą poprawność: /const
mogli nazwać to, czy obiekt lambda jest const. Na przykład mogą przekazać go do funkcji przyjmującejstd::function<void()> const&
. Aby umożliwić lambdzie zmianę przechwyconych kopii, w początkowych dokumentach członkowie danych zamknięcia zostali zdefiniowanimutable
wewnętrznie automatycznie. Teraz musisz ręcznie wprowadzićmutable
wyrażenie lambda. Nie znalazłem jednak szczegółowego uzasadnienia.FWIW, Herb Sutter, znany członek komitetu normalizacyjnego C ++, podaje inną odpowiedź na to pytanie w kwestiach poprawności i użyteczności lambda :
Jego artykuł mówi o tym, dlaczego należy to zmienić w C ++ 14. Jest krótki, dobrze napisany, wart przeczytania, jeśli chcesz wiedzieć „co jest w umyśle [członka komitetu]” w odniesieniu do tej konkretnej funkcji.
źródło
Musisz pomyśleć, jaki jest typ zamknięcia twojej funkcji Lambda. Za każdym razem, gdy deklarujesz wyrażenie Lambda, kompilator tworzy typ zamknięcia, który jest niczym innym jak nienazwaną deklaracją klasy z atrybutami ( środowisko, w którym deklarowane jest wyrażenie Lambda) i
::operator()
zaimplementowane wywołanie funkcji . Podczas przechwytywania zmiennej przy użyciu funkcji kopiowania według wartości kompilator utworzy nowyconst
atrybut w typie zamknięcia, więc nie można go zmienić w wyrażeniu Lambda, ponieważ jest to atrybut „tylko do odczytu”, dlatego nazwij to „ zamknięciem ”, ponieważ w pewien sposób zamykasz wyrażenie Lambda, kopiując zmienne z górnego zakresu do zakresu Lambda.mutable
, przechwycona jednostka stanie sięnon-const
atrybutem typu zamknięcia. To powoduje, że zmiany dokonane w zmiennej zmiennej przechwytywanej przez wartość nie są propagowane do górnego zakresu, ale pozostają w stanie Lambda. Zawsze próbuj sobie wyobrazić wynikowy typ zamknięcia twojego wyrażenia Lambda, który bardzo mi pomógł i mam nadzieję, że może ci również pomóc.źródło
Zobacz ten projekt , pod 5.1.2 [expr.prim.lambda], podrozdział 5:
Edytuj w komentarzu litba: Może pomyśleli o przechwytywaniu według wartości, aby zewnętrzne zmiany zmiennych nie były odzwierciedlone w lambda? Referencje działają w obie strony, więc to moje wyjaśnienie. Nie wiem, czy to jest dobre.
Edytuj w komentarzu kizzx2: Najczęściej kiedy lambda ma być używana, jest funktorem algorytmów. Domyślna
const
ness pozwala na używanie jej w stałym środowisku, tak jakconst
można tam zastosować funkcje o normalnych kwalifikacjach, ale nie mogą to robić funkcjeconst
niekwalifikowane. Może po prostu postanowili uczynić to bardziej intuicyjnym dla tych przypadków, którzy wiedzą, co dzieje się w ich umyśle. :)źródło
const
domyślnie? Mam już nową kopię, wydaje się dziwne, że nie mogę jej zmienić - zwłaszcza że nie jest to w zasadzie coś złego - po prostu chcą, żebym dodałmutable
.var
jako słowo kluczowe, aby pozwolić na zmianę i stały się domyślnymi ustawieniami dla wszystkiego innego. Teraz tego nie robimy, więc musimy z tym żyć. IMO, C ++ 2011 wyszło całkiem nieźle, biorąc pod uwagę wszystko.n
to nie tymczasowy. n jest członkiem funkcji lambda utworzonej za pomocą wyrażenia lambda. Domyślnym oczekiwaniem jest to, że wywołanie lambda nie modyfikuje jej stanu, dlatego jest stałe, aby zapobiec przypadkowej modyfikacjin
.źródło
Musisz zrozumieć, co oznacza przechwytywanie! przechwytuje, a nie argumentuje! spójrzmy na kilka przykładów kodu:
Jak widać, mimo że
x
zmieniono20
na lambda, wciąż zwraca 10 (x
wciąż jest5
w lambdzie). Zmianax
w lambda oznacza zmianę samej lambdy przy każdym wywołaniu (lambda mutuje przy każdym wywołaniu). W celu wymuszenia poprawności standard wprowadziłmutable
słowo kluczowe. Określając lambda jako zmienną, mówisz, że każde wywołanie lambda może spowodować zmianę w samej lambda. Zobaczmy inny przykład:Powyższy przykład pokazuje, że poprzez zmodulowanie lambdy, zmiana
x
wewnątrz lambdy „mutuje” lambda przy każdym wywołaniu nową wartościąx
, która nie ma nic wspólnego z rzeczywistą wartościąx
funkcji głównejźródło
Obecnie istnieje propozycja zmniejszenia zapotrzebowania na
mutable
deklaracje lambda: n3424źródło
mutable
jest to nawet słowo kluczowe w C ++.Aby rozszerzyć odpowiedź Puppy, funkcje lambda mają być funkcjami czystymi . Oznacza to, że każde wywołanie o unikalnym zestawie wejściowym zawsze zwraca ten sam wynik. Zdefiniujmy dane wejściowe jako zbiór wszystkich argumentów plus wszystkie przechwycone zmienne po wywołaniu lambda.
W funkcjach czystych wyjście zależy wyłącznie od danych wejściowych, a nie od stanu wewnętrznego. Dlatego każda funkcja lambda, jeśli jest czysta, nie musi zmieniać swojego stanu i dlatego jest niezmienna.
Kiedy lambda przechwytuje przez odniesienie, pisanie na przechwyconych zmiennych jest obciążeniem dla koncepcji czystej funkcji, ponieważ wszystko, co powinna zrobić czysta funkcja, to zwrócenie wyniku, chociaż lambda na pewno nie mutuje, ponieważ pisanie dzieje się ze zmiennymi zewnętrznymi. Nawet w tym przypadku poprawne użycie oznacza, że jeśli lambda zostanie wywołana ponownie z tym samym wejściem, wynik będzie za każdym razem taki sam, pomimo tych skutków ubocznych zmiennych typu by-ref. Takie skutki uboczne to tylko sposoby na zwrócenie dodatkowych danych wejściowych (np. Aktualizacja licznika) i można je przeformułować na czystą funkcję, na przykład zwracając krotkę zamiast pojedynczej wartości.
źródło