Capturing this Pointer by Value vs Reference in C++
In C++, lambda expressions frequently interact with member variables
of an enclosing class, requiring the capture of the calling instance.
While you cannot capture the this pointer itself by
reference due to language rules, you can capture the instance by
reference semantics via the pointer ([this]) or by value
using copy semantics ([*this]). Choosing between these two
mechanisms fundamentally impacts memory safety, object lifetime, and
whether operations affect the original object or an isolated copy.
Understanding the Syntax:
[this] vs [*this]
In C++, this is an rvalue expression representing a
pointer to the current instance; therefore, the syntax
[&this] is ill-formed and will not compile. Instead,
the distinction between "reference" and "value" semantics is expressed
as follows:
- Reference semantics (
[this]or[&]): Captures thethispointer by value, which provides reference access to the underlying object. - Value semantics (
[*this]): Introduced in C++17, this captures a copy of the entire dereferenced object into the lambda closure.
Consequences
of Capturing via [this] (Pointer / Reference
Semantics)
When you capture [this], the lambda stores a copy of the
pointer pointing to the existing instance.
- Direct Mutation: Any member variable modified inside the lambda directly mutates the original object's state.
- Zero Copy Overhead: Only the size of a pointer (typically 8 bytes on a 64-bit architecture) is stored in the closure object.
- Dangling Pointer Risks: If the lambda outlives the enclosing object—such as when passed to an asynchronous thread, an event queue, or a detached task—invoking the lambda results in undefined behavior because the pointer will point to deallocated memory.
Consequences
of Capturing via [*this] (Object Value Semantics)
When using [*this], the closure creates an independent
copy (or move) of the enclosing class instance at the moment the lambda
is constructed.
- Thread and Lifetime Safety: The lambda carries its own copy of the object state. It remains entirely valid even if the original object that created it is destroyed. This makes it ideal for concurrent programming and asynchronous callbacks.
- Isolation of State: Modifications made inside the
lambda only affect the copy, leaving the original object unchanged. Note
that the lambda must be marked
mutableto modify non-static member variables of the copied instance. - Performance Overhead: The class must be copyable or movable. If the class manages substantial resources or holds large data buffers, copying can incur significant performance costs.
Implicit Captures: [=] vs
[&]
The behavior of default capture modes has evolved to prevent common memory bugs:
- Default by Reference (
[&]): Implicitly capturesthisas a pointer. It carries the same lifetime risks as explicit[this]. - Default by Value (
[=]): In C++11 and C++14, writing[=]implicitly captured thethispointer—not the object itself. Because this often led developers to mistakenly believe the object was safely copied for asynchronous tasks, implicit capture ofthisvia[=]was deprecated in C++20. Modern code should explicitly specify either[=, this]or[=, *this].
Summary Guidelines
- Use
[this]when the lambda's execution is guaranteed to be synchronous and within the scope of the enclosing object's lifetime. - Use
[*this]when the lambda is executed asynchronously, dispatched to another thread, or retained beyond the scope of the calling class instance.