2016-10-14 53 views
4

我正在實現我自己的類,它提供了其成員的惰性初始化。我遇到了一個在lambda中捕獲this的奇怪行爲。Lambda的「這個」捕獲返回垃圾

下面是一個重現此錯誤的示例。

//Baz.h 
#include <memory> 
#include <functional> 
#include "Lazy.hpp" 

struct Foo 
{ 
    std::string str; 

    Foo() = default; 
    Foo(std::string str) : str(str) {} 
    Foo(Foo&& that) : str(that.str) { } 
}; 

class Baz 
{ 
    std::string str; 

    Lazy<std::unique_ptr<Foo>> foo; 

public: 
    Baz() = default; 
    Baz(const std::string& str) : str(str) 
    { 
     //lazy 'this->foo' initialization. 
     //Is capturing of 'this' valid inside ctors???. 
     this->foo = { [this] { return buildFoo(); } }; 
    } 
    Baz(Baz&& that) : foo(std::move(that.foo)), str(that.str) { } 

    std::string getStr() const 
    { 
     return this->foo.get()->str; 
    } 

private: 
    std::unique_ptr<Foo> buildFoo() 
    { 
     //looks like 'this' points to nothing here. 
     return std::make_unique<Foo>(str); //got error on this line 
    } 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    ///Variant 1 (lazy Foo inside regular Baz): 
    Baz baz1("123"); 
    auto str1 = baz1.getStr(); 

    ///Variant 2 (lazy Foo inside lazy Baz): 
    Lazy<Baz> lazy_baz = { [](){ return Baz("123"); } }; 
    auto& baz2 = lazy_baz.get(); //get() method returns 'inst' member (and initialize it if it's not initialized) see below 
    auto str2 = baz2.getStr(); 

    return 0; 
} 

變1效果很好。

變體2崩潰,此錯誤:

Unhandled exception at 0x642DF4CB (msvcr120.dll) in lambda_this_capture_test.exe: 0xC0000005: Access violation reading location 0x00E0FFFC.

我使用VC++編譯器120(從VS2013)。

這裏是我的簡化Lazy類:

#pragma once 
#include <memory> 
#include <atomic> 
#include <mutex> 
#include <functional> 
#include <limits> 

template< 
    class T, 
     typename = std::enable_if_t< 
     std::is_move_constructible<T>::value && 
     std::is_default_constructible<T>::value 
     > 
> 
class Lazy 
{ 
    mutable std::unique_ptr<T> inst; 
    std::function<T(void)> func; 
    mutable std::atomic_bool initialized; 
    mutable std::unique_ptr<std::mutex> mutex; 

public: 
    Lazy() 
     : mutex(std::make_unique<std::mutex>()) 
     , func([]{ return T(); }) 
    { 
     this->initialized.store(false); 
    } 

    Lazy(std::function<T(void)> func) 
     : func(std::move(func)) 
     , mutex(std::make_unique<std::mutex>()) 
    { 
     this->initialized.store(false); 
    } 
//... <move ctor + move operator> 
    T& get() const 
    { 
     if (!initialized.load()) 
     { 
      std::lock_guard<std::mutex> lock(*mutex); 

      if (!initialized.load()) 
      { 
       inst = std::make_unique<T>(func()); 
       initialized.store(true); 
      } 
     } 

     return *inst; 
    } 
}; 

所以我的問題是:爲什麼這個例子crashe?在構造函數中捕獲this有效嗎?

回答

8

通常,在構造函數中捕獲this是有效的。但是當這樣做時,你必須確保lambda不會超過它捕獲的對象。否則,捕獲的this將成爲懸掛指針。

這正是你的情況。所述Bazthis被捕獲是通過return Baz("123")創建的main -scoped拉姆達(一個內部構造的暫時的。然後,當內部Lazy<Baz>創建Baz,所述std::function是從臨時BazBaz通過Lazy<Baz>::inst指出,移動但裏面所拍攝的this移動拉姆達仍然指向原來,臨時Baz對象。該對象則超出範圍和威猛,你有一個懸擺指針。

註釋由Donghui Zhang(使用enable_shared_from_this並捕獲shared_ptr以及this)爲您的問題提供了一種可能的解決方案。您的Lazy<T>類將實例存儲爲std::unique_ptr<T>所擁有的實例。如果將仿函數簽名更改爲std::function<std::unique_ptr<T>()>,則將擺脫該問題,因爲由懶惰初始化程序創建的對象將與Lazy中存儲的對象相同,因此捕獲的this不會過早過期。

+1

要添加到@Angew所說的內容,我有時會讓我的類從std :: enable_shared_from_this派生,並讓lambda函數捕獲使用shared_from_this()創建的「this」和shared_ptr。捕獲shared_ptr可確保對象超出lambda函數。捕獲「this」是多餘的,但它使得lambda函數更容易訪問成員數據。 –

+0

@DonghuiZhang這當然需要動態分配對象。當然,這可以減輕「this」過期的其他問題。 – Angew

0

問題是,捕獲的this是一個特定的對象。您可以複製lambda而不更改捕獲的thisthis然後懸垂,你的代碼中斷。

你可以使用智能指針來管理這個;但你可能反而想重新設定它。

我會修改Lazy。懶惰需要以及T

我會給它一個簽名。

template< 
    class Sig, class=void 
> 
class Lazy; 

template< 
    class T, 
    class...Sources 
> 
class Lazy< 
    T(Sources...), 
    std::enable_if_t< 
    std::is_move_constructible<T>::value && 
    std::is_default_constructible<T>::value 
    > 
> 
{ 
    std::function<T(Sources...)> func; 
    // ... 
    Lazy(std::function<T(Sources...)> func) 
    // ... 
    T& get(Sources...srcs) const { 
    // ... 
      inst = std::make_unique<T>(func(std::forward<Sources>(srcs)...)); 
    // ... 

現在Baz

Lazy<std::unique_ptr<Foo>(Baz const*)> foo; 

與調整,以構造函數和getStr

Baz(const std::string& str) : str(str) 
{ 
    this->foo = { [](Baz const* baz) { return baz->buildFoo(); } }; 
} 

std::string getStr() const 
{ 
    return this->foo.get(this)->str; 
} 

main我們國家我們Baz來自任何來源的數據:

Lazy<Baz()> lazy_baz = { []{ return Baz("123"); } };