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 the this pointer 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 mutable to 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 captures this as a pointer. It carries the same lifetime risks as explicit [this].
  • Default by Value ([=]): In C++11 and C++14, writing [=] implicitly captured the this pointer—not the object itself. Because this often led developers to mistakenly believe the object was safely copied for asynchronous tasks, implicit capture of this via [=] 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.