2016-05-10 76 views
1

類型考慮下面的代碼:Lambda表達式,共享指針和本

#include <memory> 
#include <cassert> 

struct S: std::enable_shared_from_this<S> { 
protected: 
    S() = default; 

    int bar() { return 42; } 
}; 

struct T: S { 
    static std::shared_ptr<T> create() { 
     return std::shared_ptr<T>(new T{}); 
    } 

    auto foo() { 
     auto ptr = std::static_pointer_cast<T>(shared_from_this()); 
     return [ptr](){ return ptr->bar(); }; 
    } 

private: 
    T() = default; 
}; 

int main() { 
    std::shared_ptr<T> ptr = T::create(); 
    auto lambda = ptr->foo(); 
    assert(lambda() == 42); 
} 

以上代碼編譯。它不如果該方法foo被修改,因爲它遵循:

auto foo() { 
    // In this case, there is no explicit cast 
    // The type of ptr is no longer std::shared_ptr<T> 
    // It is std::shared_ptr<S> instead 
    auto ptr = shared_from_this(); 
    return [ptr](){ return ptr->bar(); }; 
} 

在這種情況下,代碼不再編譯(既不與GCC也不與鐺)。

顯然,這將鑄造後編譯(這是我的第一個例子),但我預計bar要到拉姆達可見,即使在這種情況下,它是在它的上下文訪問,並且接口的一部分也是S

,我懷疑這是由於5.1.5p8,特別是:

拉姆達表達的複合語句產生了函數體的函數調用操作符的[...],但[.. 。],確定這個的類型和值,複合語句被認爲是在lambda表達式的上下文中。

事實上,通過鐺返回的錯誤是很清楚:

main.cpp中:8:9:注意:只能類型的對象上訪問構件T

我的推導是否正確?
是否由於提到的段落,因此this指針的確定類型的問題與共享指針之一不匹配?

事實上,shared_ptr參與遊戲使我有點難以理解。
老實說,我希望這兩個例子都可以編譯,或者兩者都會失敗。

+1

你的'static_pointer_cast'消失了。另外,'返回[ptr = shared_from_this()](){返回ptr->酒吧(); };'行爲不同? – ildjarn

+0

'shared_from_this'爲'S'返回一個共享指針,它不再被轉換爲'T'的共享指針。所以實際上,第二個例子中'ptr'的類型是'std :: shared_ptr '。你想讓我以更可讀的方式寫嗎? – skypjack

+0

[Works for me](http://melpon.org/wandbox/permlink/hl00GHSN4JZ1Qhi1)經過一些簡單的修改。 –

回答

3

看起來你只是簡單地違反了受保護訪問的基本規則。即整個事情與lambda或共享指針無關。自從時代開始以來,受保護訪問的古老規則說基類的受保護成員只能通過派生類的對象訪問。與上述內容相反,在S類型的受保護成員中,S類型的對象無法訪問,但可通過類型爲T的對象訪問。

整個事情可以減少下面的簡單示例

struct S 
{ 
protected: 
    int i; 
}; 

struct T : S 
{ 
    void foo() 
    { 
     this->i = 5; // OK, access through `T` 

     T t; 
     t.i = 5; // OK, access through `T` 

     S s; 
     s.i = 5; // ERROR: access through `S`, inaccessible 

     S *ps = this; 
     ps->i = 5; // ERROR: access through `S`, inaccessible 
    } 
}; 
+1

這更像是'S * s = this; s-> i = 5;',呃......你說得對。拉米達確實很簡單,讓我吝嗇失敗。 – skypjack

3

我覺得在問題提出的意見有答案,而且我並不想邀功這裏。

我想你可能會感興趣,而無需實際調用靜態澆鑄,甚至無需知道基類進行靜態澆鑄的工作的一個「更好」的方式:

首先定義這個有用的免費功能:

template<class T> 
auto shared_from_that(T* p) 
{ 
    return std::shared_ptr<T>(p->shared_from_this(), p); 
} 

然後讓你輸入正確的共享指針而言吧:

auto foo() { 
    return [ptr = shared_from_that(this)](){ 
     return ptr->bar(); 
    }; 
} 

簡介:

調用std::shared_ptr的2個參數的構造函數,它使用arg1中shared_ptr的控制塊和指向arg2中受控對象的指針。

+0

你還是應該得到這個好戲。謝謝。除了使用靜態轉換或者它只是另一種方式之外,這種方式(除了基於意見的答案之外,我的意思是在性能或其他方面)? – skypjack

+0

@skypjack在性能方面它可能比static_pointer_cast更好或更差,但它需要較少的維護,因爲(a)你不需要知道你的類的類型[因此在重構期間更少的錯誤]和(b) )它會自動正確地遵循'const'。 –