2016-07-13 82 views
24

我在限定在局部靜態變量的λ,並將其存儲的類的功能:什麼時候在lambda中捕獲「this」?

class A 
{ 
public: 
    void call_print() 
    { 
     static auto const print_func = [this] { 
      print(); 
     }; 

     print_func(); 
    }; 

    virtual void print() 
    { 
     std::cout << "A::print()\n"; 
    } 
}; 

class B : public A 
{ 
public: 
    virtual void print() override 
    { 
     std::cout << "B::print()\n"; 
    } 
}; 

我還執行以下測試:

int main() 
{ 
    A a; 
    B b; 

    a.call_print(); 
    b.call_print(); 
} 

Live Sample

我期望打印的是:

A::print() 
B::print() 

但我真正得到的是:

A::print() 
A::print() 

(同一個對象的地址還印有每個)

我懷疑這是由於this捕獲。我認爲它會在調用時捕獲this的值,但它似乎在lambda定義的時刻被捕獲。

有人可以解釋lambda捕獲的語義?他們什麼時候才真正獲得該功能?所有捕捉類型都是一樣的,還是this是特例?刪除static解決了這個問題,但是在我的生產代碼中,我實際上將lambda存儲在稍微重一點的對象中,該對象表示稍後插入信號的插槽。

回答

36

這與lambda捕獲的語義無關。這只是static的工作原理。

A static函數範圍變量初始化恰好一次。整個程序中只有一個這樣的對象。它將在第一次調用該函數時被初始化(更具體地說,第一次執行static語句)。因此,用於初始化static變量的表達式只會被調用一次。

因此,如果一個static函數範圍變量初始化爲基於某個函數參數(如this)的數據,那麼它將只從第一次調用該函數時獲取參數。

您的代碼會創建一個lambda。它不會在函數的每次調用時創建不同的lambda表達式。

您似乎想要的行爲不是函數本地static變量,而是對象成員。因此,只需將std::function對象放入類本身,並且如果該對象爲空,則會將其初始化爲。

+11

這可能是值得補充說,這其實是一個非常危險的編程風格,因爲如果'a'被刪除,未來對'call_print'的調用將會調用未定義的行爲並且很可能崩潰。 – Xirema

+0

@Xirema這是非常真實的,但我不認爲這是他想要得到的行爲。 –

+0

我明白'靜態'的語義,我試圖弄清楚的是,如果lambda「承諾」捕獲'this * * *以後*,或者如果它被捕獲*正確然後*。不過,您在回覆中確實解決了這個問題。我希望lambda的容器是靜態的,但不是捕獲的。我希望他們在定義和調用之間分開。 –

2

事實上,捕獲的值是在lambda被定義時設置的,而不是被調用的。因爲您要爲定義lambda表達式的表達式設置一個靜態變量,所以這隻會在第一次調用函數call_print(由控制靜態變量的規則)時發生。因此,其後所有的call_print調用實際上獲得相同的lambda,其this被設置爲&a的那個。

12

lambda在第一次調用封閉函數A::call_print()時被實例化。自從您第一次在A對象上調用該對象時,將捕獲該對象的this。如果顛倒調用的順序,你會看到一個不同的結果:

b.call_print(); 
a.call_print(); 

輸出:

B::print() 
B::print() 

它更多的是與函數局部比那些靜態對象的初始化的語義lambda捕獲。

8

靜態局部變量在其聲明第一次執行時被初始化。

在這種情況下,您:

  1. 創建拉姆達捕捉& A作爲此,調用A.print();
  2. 分配一個lambda來print_func
  3. 調用拉姆達(通過print_func
  4. 調用拉姆達一次。

創建lambda時捕獲的值總是被捕獲 - 在這種情況下,在第一次調用call_print期間。

4

我不認爲這裏的捕獲是問題,而是static關鍵字。 想想你的代碼是這樣的:

class A 
{ 
public: 
    void call_print() 
    { 
     static A* ptr = this; 

     ptr->print(); 
    }; 

    virtual void print() 
    { 
     std::cout << "A::print()\n"; 
    } 
}; 

class B : public A 
{ 
public: 
    virtual void print() override 
    { 
     std::cout << "B::print()\n"; 
    } 
}; 

這幾乎沒有拉姆達一樣。

如果你看一下它的代碼變得很清楚,你的電話a.call_print();的指針,然後再使用該對象a初始化ptr

3

這個問題不是關於lambdas的行爲,而是關於函數中靜態變量的行爲。

該變量是在第一次代碼流過它時創建的,使用當時可用的任何變量。

例如:

#include <iostream> 

int foo(int v) 
{ 
    static int value = v; 
    return v; 
}; 

int main() 
{ 
    std::cout << foo(10) << std::endl; 
    std::cout << foo(11) << std::endl; 
} 

預期:

10 
10 

因爲它等同於:

foo::value = 10; 
std::cout << foo::value << std::endl; 
// foo::value = 11; (does not happen) 
std::cout << foo::value << std::endl; 
相關問題