C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
85: Lambda Captures in Depth
Imagine you're sending a field agent out on a mission. The agent is the lambda function. Now, the agent can't possibly bring the entire headquarters with them—that would be inefficient and impossible. Instead, they carry a "mission kit" (the capture clause). Some items in that kit are just photos of documents back at HQ (captured by value); if the agent scribbles on the photo, the original document at HQ stays clean. Other items are high-frequency radios (captured by reference); if the agent transmits a change through the radio, the people back at HQ see that change in real-time.
In C++, the capture clause [] is exactly that kit. It tells the compiler which variables from the surrounding scope need to be "packed" into the lambda's internal structure so they're available when the lambda is actually executed, potentially long after the original scope has vanished.
Snapshots versus Tethers
When you use [=], you're telling the lambda to take a snapshot of everything it needs. It's safe, but it can be slow if you're copying a massive std::vector. When you use [&], you're creating a tether. This is fast, but it's where most of the "weird" bugs live.
void runFilter(const std::vector<int>& scores, int threshold) {
// Capture 'threshold' by value because it's a small int.
// We don't need to change the original threshold.
auto isHighScorer = [threshold](int s) {
return s > threshold;
};
// If we wanted to count how many passed, we'd need a tether to a counter.
int count = 0;
auto incrementCount = [&count]() {
count++;
};
for(int s : scores) {
if(isHighScorer(s)) incrementCount();
}
std::cout << "High scorers: " << count << "\n";
}
I usually advise against using the "capture all" shortcuts [=] or [&] in production code. It's too easy to accidentally capture a heavy object by value or a temporary variable by reference. Be explicit. If you need threshold and count, write [threshold, &count]. It makes your intentions clear to the next developer (which might be you in six months).
The Dangling Reference Trap
Here is the mistake I see most often: returning a lambda that captures a local variable by reference. If the lambda outlives the function it was created in, that reference points to a memory location that has been reclaimed. It's a classic "use-after-free" scenario.
If you're returning a lambda from a function to be used as a callback, always capture by value. If you need the lambda to modify something, that "something" needs to live in a std::shared_ptr or be passed in as an argument during the call, not captured from the stack.
Bridging the Gap to Class Members
Lambdas inside class methods are a bit special. You can't actually capture individual member variables (like this->m_score) directly. You have to capture the this pointer.
In C++11/14, [this] gives you a reference to the current object. But if the object is deleted before the lambda runs, you're back to the dangling reference problem. C++17 introduced [*this], which creates a full copy of the object. It's a lifesaver for asynchronous code where the object might be destroyed while a background task is still processing.
class GameSession {
int sessionID = 101;
auto getLogger() {
// Captures the object by value (C++17)
// Even if GameSession is destroyed, the lambda has its own copy of sessionID.
return [*this]() {
std::cout << "Log from session: " << sessionID << "\n";
};
}
};
Moving Heavy Objects into Captures
Sometimes you have a std::unique_ptr or a massive buffer that you can't copy, but you can't leave it in the local scope either. This is where "init-captures" (C++14) come in. You can actually move a variable into the lambda's scope.
auto createProcessor() {
auto data = std::make_unique<std::vector<int>>(1000000);
// We move 'data' into the lambda.
// 'data' is now null in the outer scope, and owned by the lambda.
return [buffer = std::move(data)]() {
std::cout << "Processing " << buffer->size() << " elements\n";
};
}
📋 Practical Task
Implementing a Dynamic Inventory Filter
You are building a system for an RPG inventory. You need to create a function that returns a "Filter Lambda." This lambda should be able to check if an item meets two criteria: a specific category (string) and a minimum value (int). Both the category and the minimum value are determined at the moment the filter is created, not when it is used.
Requirements:
- Define a simple
Itemstruct withstd::string name,std::string category, andint value. - Write a function
createItemFilter(std::string cat, int minVal)that returns a lambda. - The lambda must capture
catandminValby value to ensure the filter remains valid even aftercreateItemFilterreturns. - The lambda should take a
const Item&as an argument and returntrueif the item matches the category AND its value is greater than or equal to the minimum value. - In your
main, create astd::vector<Item>, generate a filter for "Weapon" with a value of 50, and usestd::count_ifor a loop to find how many items match.
There are no comments for now.