如果一個成員函式(Member Function)有 Lambda Expression 而且該 Lambda Expression 有指定預設補捉模式(不論是傳值或傳參考)。當 Lambda Expression 內有程式碼指稱「成員函式」或「資料成員」時,this 會被隱含地加入 Capture List。Lambda Expression 在執行時會透過 this 指標存取成員函式或資料成員:
#include <cassert>
class Example {
private:
int value_;
public:
Example(int value = 0) : value_(value) {}
void run() {
auto f = [&]() { ++value_; };
auto g = [=]() { ++value_; };
assert(value_ == 0);
f();
assert(value_ == 1);
g();
assert(value_ == 2);
}
};
int main() {
Example x;
x.run();
return 0;
}
然而在一些情境下,我們會希望被補捉的不只是 this 指標。我們想要捕捉整個 this 指向的物件。例如以下 C++ 11 程式碼:
#include <cmath>
#include <future>
#include <iostream>
class Task {
private:
double a_;
double b_;
public:
Task() : a_(0.0), b_(0.0) {}
~Task() {
// Clear data members to show use-after-free problem
a_ = std::nan("");
b_ = std::nan("");
}
void set_param_a(double a) { a_ = a; }
void set_param_b(double b) { b_ = b; }
std::future<double> run() {
Task self = *this;
return std::async(std::launch::async, [=]() {
return std::sqrt(self.a_ * self.a_ + self.b_ * self.b_);
});
}
};
std::future<double> start() {
Task t;
t.set_param_a(5.0);
t.set_param_b(12.0);
return t.run();
}
int main() {
std::cout << start().get() << std::endl;
}
首先我們先看 run 函式。假設 run 函式要進行複雜的運算,所以使用 std::async 函式將計算工作交給另一個執行緒(Thread)。當另一個執行緒完成計算後,會將計算結果填入回傳的 std::future<double> 物件。因為計算過程要使用 Task 的成員變數,所以我們先將 *this 複製為 self,再讓 Lambda Expression 捕捉 self 變數。
在這個例子中,我們不能直接捕捉 this,因為被捕捉的 this 只是指標。受限於 start 函式的實作,this 指向的物件有可能會在 Lambda Expression 執行之前就被解構。
在 C++ 14 run 函式能直接利用「Lambda Capture Initializer」初始化 self 並減少一次複製建構(如下)。然而我們仍需要透過 self. 存取成員變數。如果我們忘記加上 self.,編譯器會自動捕捉 this 指標,進而產生非預期的錯誤。
std::future<double> run() {
return std::async(std::launch::async, [=, self=*this]() {
return std::sqrt(self.a_ * self.a_ + self.b_ * self.b_);
});
}
在 C++ 17,我們能直接在 Lambda Expression 的 Capture List 指定 *this。這代表我們要將 this 指標指向的物件以傳值的方式傳入 Lambda Expression。在 Lambda Expression 內,我們能直接以資料成員的名稱(本例中分別為 a_ 與 b_)存取資料成員:
std::future<double> run() {
return std::async(std::launch::async, [=, *this]() {
return std::sqrt(a_ * a_ + b_ * b_);
});
}
Capture List 規則
在 C++ 17 Capture List 與 this 的規則改為:
[&]隱含地抓取this指標。[=]隱含地抓取this指標。[&, this]明確地抓取this指標。與[&]效果相同。[=, *this]明確地抓取「this指標指向的物件」。[=, this]在 C++ 20 是明確地抓取this指標。與[=]相同。如果你只想捕捉this指標,而不是「this指標指向的物件」,C++ 20 建議改用這個寫法。然而在 C++ 17 這是錯誤語法。(參見 P0409R2)[this, *this]是錯誤語法。
另外,在一個巢狀 Lambda Expression 之中,如果一個 Lambda Expression 想要捕捉 this 指標或 *this,上一層的 Lambda Expression 也必需捕捉 this 或 *this:
class ExampleNested {
private:
int value_;
public:
ExampleNested(int value = 0) : value_(value) {}
int test() {
auto f = [this]() {
return [*this]() {
return value_;
};
};
auto g = [*this]() {
return [this]() {
return value_;
};
};
auto h = [*this]() {
return [*this]() {
return value_;
};
};
return f()() + g()() + h()();
}
};
而以下程式碼會產生編譯錯誤:
class ExampleNestedBad {
private:
int value_;
public:
ExampleNestedBad(int value = 0) : value_(value) {}
int test() {
auto f = []() {
return [*this]() { // error: `this` is not captured by f
return value_;
};
};
return f()();
}
};





