5.2.22 · D3 · Coding › C++ Programming › Lambda expressions — capture list (by value, by reference)
Intuition Yeh deep dive kya karta hai
Parent note ne tumhe do ideas sikhaye: ==value = ek photo, reference = ek phone line==. Yeh page har us case ko dhundh ke nikalta hai jo un dono ideas se ban sakti hai — har capture mode, har lifetime, har "gotcha" — aur har ek ko tab tak work karta hai jab tak koi bhi scenario tumhe surprise na kar sake.
Agar koi term naya lage, toh woh parent note mein earn kiya gaya tha: ek capture woh variable hai surrounding code se jise lambda dekhne ki permission rakhta hai; by value lambda ke andar ek private copy store karta hai; by reference original ka ek alias (ek address) store karta hai.
Capture lists ke baare mein har sawaal actually do axes ke baare mein sawaal hai:
Kaise capture kiya? (copy vs alias, explicit vs default, mutable hai ya nahi)
Kiski lifetime zyada lambi hai — lambda ki ya us variable ki jise usne capture kiya?
Inhe cross karo aur tumhe complete grid milti hai. Neeche har cell ek example mein work ki gayi hai.
#
Cell
Capture
Key twist
Example
A
Snapshot
[x]
original creation ke baad change hota hai
Ex 1
B
Live wire
[&x]
original creation ke baad change hota hai
Ex 2
C
Editable copy
[x] mutable
copy calls ke across persist karti hai
Ex 3
D
Mixed default
[=, &b]
kuch copied, kuch aliased
Ex 4
E
Dangling (degenerate)
[&x] scope se escape kare
dead variable ka reference
Ex 5
F
Zero / empty
[]
kuch capture nahi — compile behaviour
Ex 6
G
Snapshot ki timing
[x] loop mein create hoi
copy kab li jaati hai
Ex 7
H
Real-world word problem
[&total]
running sum accumulate karna
Ex 8
I
Exam twist
[this] vs [*this]
object ko capture karna
Ex 9
Neeche ki picture ek nazar mein poora matrix hai — padhte waqt ise khula rakho.
Worked example Ex 1 — Cell A: snapshot
[x]
int price = 100 ;
auto quote = [ price ]() { return price; };
price = 250 ;
std ::cout << quote (); // ?
Forecast: aage padhne se pehle printed number guess karo.
[price] price ko lambda ke hidden member mein copy karta hai. Yeh step kyun? By value = ek photo. Photo [price] line par li jaati hai, jahan price 100 hai.
price = 250 sirf outside variable ko edit karta hai. Yeh step kyun? Lambda ki copy memory mein ek alag object hai — assignment usse reach nahi kar sakta.
quote() private copy ko read karta hai → 100.
Verify: copy 100 par frozen thi, kisi ne use touch nahi kiya, isliye 100 hi ek possible answer hai. ✔
Worked example Ex 2 — Cell B: live wire
[&x]
int price = 100 ;
auto quote = [ & price ]() { return price; };
price = 250 ;
std ::cout << quote (); // ?
Forecast: Ex 1 jaisa hi code lekin & ke saath. Number guess karo.
[&price] price ka address store karta hai. Yeh step kyun? By reference = ek phone line. Koi copy nahi banti; lambda yaad rakhta hai price kahan rehta hai.
price = 250 us exact memory mein 250 likhta hai.
quote() address ko dereference karta hai aur jo abhi wahan hai woh read karta hai → 250. Yeh step kyun? Har call original ko re-read karti hai, isliye yeh hamesha current value dekhta hai.
Verify: alias aur original naam same memory ke hain; last write jeet ta hai → 250. ✔
Worked example Ex 3 — Cell C: editable copy
[x] mutable
int seed = 10 ;
auto next = [ seed ]() mutable { seed += 5 ; return seed; };
int a = next (); // ?
int b = next (); // ?
std ::cout << seed; // ?
Forecast: a, b, aur final seed predict karo.
[seed] 10 ko member mein copy karta hai. Yeh step kyun? Creation time par photo.
mutable operator() par default const hata deta hai. Yeh step kyun? Iske bina copy read-only hoti aur seed += 5 compile nahi hota. mutable tumhe copy edit karne deta hai — original ko kabhi nahi.
Pehli call: copy 10 + 5 = 15, returns 15. Member 15 rakhta hai (closure ek persistent object hai).
Doosri call: copy 15 + 5 = 20, returns 20.
Bahar ka seed untouched → 10.
Verify: a = 15, b = 20, seed = 10. Copy 10→15→20 climb karti hai; original kabhi nahi hila. ✔
Worked example Ex 4 — Cell D: mixed default
[=, &b]
int a = 1 , b = 2 ;
auto m = [ = , & b ]() mutable { a += 10 ; b += 10 ; return a + b; };
int r = m (); // ?
std ::cout << a; // ?
std ::cout << b; // ?
Forecast: r predict karo, phir call ke baad a aur b.
[=, &b] = "default by value, except b by reference." Yeh step kyun? a use ho raha hai aur koi exception listed nahi hai, isliye yeh copy hota hai; b explicitly aliased hai.
Andar: a ki copy 1 + 10 = 11 ban jaati hai. Yeh step kyun? mutable copy edit karne deta hai.
b += 10 alias ke through likhta hai: original b 2 + 10 = 12 ban jaata hai.
Returns 11 + 12 = 23.
Call ke baad: bahar ka a abhi bhi 1 hai (sirf copy badi); bahar ka b 12 hai (reference ke zariye edit hua).
Verify: r = 23, a = 1, b = 12. Copy internally edit hui, alias ne real cheez ko edit kiya. ✔
Worked example Ex 5 — Cell E: dangling reference (degenerate lifetime)
auto make () {
int local = 42 ;
return [ & local ]() { return local; }; // local dies at return!
}
auto bad = make ();
// bad(); // reads dead memory -> undefined behavior
Forecast: yeh woh kyu hai jo crash karta hai?
make() apne stack par local = 42 create karta hai. Yeh step kyun? Yeh ek local variable hai — iski lifetime tab khatam hoti hai jab make() return karta hai (dekho Object lifetime and scope ).
[&local] local ka address capture karta hai. Lambda ab ek aisi phone line rakhta hai jis insaan ne building chhod di hogi.
make() return karta hai → local destroy ho jaata hai. bad ke andar ka address ab reclaimed stack memory ko point karta hai.
bad() call karna ek dead address ko dereference karta hai → undefined behaviour. Yeh step kyun? Lambda ne apne captured variable ko outlive kar diya — yahi fatal ordering hai.
Fix: by value capture karo — [local] — taaki 42 returned closure ke andar jiye. Yahi parent mein bataaya rule hai: by value capture karo jab lambda apna scope escape kare (return ho, store ho, std::thread and async ko diya jaye). References vs pointers in C++ dekho ki kaise ek dangling reference exactly ek dangling pointer ki tarah hoti hai disguise mein.
Verify: [local] ke saath returned closure apna 42 carry karta hai; bad() safely 42 return karta hai. ✔
Worked example Ex 6 — Cell F: empty capture
[]
int outside = 5 ;
auto pure = []( int x ) { return x * 2 ; }; // OK
// auto bad = []() { return outside; }; // COMPILE ERROR
std ::cout << pure ( 7 ); // ?
Forecast: kaun sa line compile karne se mana karta hai, aur accha wala kya print karta hai?
[] kuch capture nahi karta. Yeh step kyun? Empty list ek promise hai: "Main koi bahar ke locals use nahi karta."
pure sirf apne parameter x ko touch karta hai. Yeh step kyun? Parameters capture nahi hote — woh har call par fresh aate hain — isliye [] theek hai.
Commented line outside read karta hai, jo ek non-captured local hai → compile error. Yeh step kyun? Lambda ne apna [] promise toda.
pure(7) → 7 * 2 = 14.
Verify: pure(7) = 14. Capture-nothing lambda effectively ek plain functor hai bina state ke, jo freely ek std::function ya raw function pointer mein convertible hai. ✔
Worked example Ex 7 — Cell G: copy
kab li jaati hai? (loop timing)
std ::vector < std ::function <int () >> fns; // see std::function
for ( int i = 0 ; i < 3 ; ++ i) {
fns. push_back ([ i ]() { return i; }); // by VALUE
}
// fns[0](), fns[1](), fns[2]() -> ?
Forecast: teen stored lambdas kya return karte hain?
Har iteration mein i ki ek value hoti hai: 0, phir 1, phir 2. Yeh step kyun? Humein push time par snapshot value chahiye.
[i] current i ko us iteration ke closure mein copy karta hai. Yeh step kyun? By value loop counter ko waise freeze karta hai jaise woh us pass par tha.
Teen independent closures 0, 1, 2 respectively store karte hain.
Inhe call karne par 0, 1, 2 milte hain.
Ab ka trap: agar humne [&i] likha hota, toh teeno ek hi i ko alias karte. Loop ke baad i 3 hai (matlab, scope se bahar), isliye har call same stale/dead value padhta — Cell-E-style bug.
Verify: fns[0]()=0, fns[1]()=1, fns[2]()=2. By value har iteration ko snapshot karta hai; by reference ek doomed counter share karta. ✔
Worked example Ex 8 — Cell H: real-world word problem (running total)
Problem. Ek cashier items scan karta hai prices {40, 30, 55, 25} ke saath. Ek aisa lambda likho jo, har item par ek baar call hone par, ek running total rakhe aur use return kare. Charo scans ke baad total kya hoga?
int total = 0 ;
auto scan = [ & total ]( int price ) { total += price; return total; };
scan ( 40 ); scan ( 30 ); scan ( 55 ); scan ( 25 );
std ::cout << total; // ?
Forecast: charo returned values aur final total guess karo.
Hum chahte hain ki lambda bahar ki duniya change kare (accumulate kare). Yeh step kyun? Ek snapshot accumulate nahi kar sakta — photo kabhi update nahi hoti. Humein live phone line chahiye → [&total].
Scan 40: total = 0 + 40 = 40.
Scan 30: total = 40 + 30 = 70.
Scan 55: total = 70 + 55 = 125.
Scan 25: total = 125 + 25 = 150.
Verify (units): rupees add hue: 40 + 30 + 55 + 25 = 150. Reference-captured total 150 hold karta hai. ✔
Worked example Ex 9 — Cell I: exam twist
[this] vs [*this]
struct Bank {
int balance = 500 ;
auto deposit_later () {
return [ this ]( int amt ) { balance += amt; return balance; };
}
};
Bank acc;
auto dep = acc. deposit_later ();
int r = dep ( 200 ); // ?
std ::cout << acc.balance; // ?
Forecast: r aur acc.balance predict karo.
[this] object ka this pointer capture karta hai — by reference-jaisi semantics. Yeh step kyun? Ek method ke andar, balance ka matlab actually this->balance hota hai; ise touch karne ke liye lambda ko this chahiye. (Dekho const member functions : ek non-const method humein balance modify karne deti hai.)
dep(200) acc.balance += 200 chalata hai → 500 + 200 = 700, returns 700.
Kyunki this real acc ko point karta hai, acc.balance ab 700 hai.
Twist / danger: [this] ek pointer hai — agar dep call hone se pehle acc destroy ho jaaye, toh yeh exactly Cell E ki tarah dangle karega. Poore object ka snapshot carry karne ke liye, [*this] (C++17) use karo, jo object ko closure mein copy karta hai. Tab lambda ke andar edits acc.balance ko 500 par chhodenge.
Verify: [this] ke saath: r = 700, acc.balance = 700. [*this] ke saath lambda apni copy edit karta, acc.balance 500 rahta. ✔
Recall Har cell, ek-ek line mein
Original change hone ke baad by value ::: frozen snapshot value abhi bhi wahi rahti.
Original change hone ke baad by reference ::: current live value milti hai.
[x] mutable do calls ke across ::: internal copy persist karti aur badhti rehti hai; original kabhi nahi hilta.
[=, &b] ::: default copy, lekin b aliased — copy edits andar rahte hain, alias edits real b tak pahunchte hain.
[&local] function se return hone par ::: dangling reference → undefined behaviour.
[] bahar ke local ko use karte hue ::: compile error; parameters theek hain though.
Loop mein [i] vs [&i] ::: value har iteration ko snapshot karta hai; reference ek (doomed) counter share karta hai.
[this] vs [*this] ::: real object ka pointer (dangle ho sakta hai) vs object ki full copy.