2017-04-06 60 views
2

想象以下代碼:如何保持shared_ptr對象的成員在異步函數中使用?

void async(connection *, std::function<void(void)>); 

void work() 
{ 
    auto o = std::make_shared<O>(); 
    async(&o->member, [] { do_something_else(); }); 
} 

async將,例如,開始使用的omember將其作爲一個指針通過的螺紋。但是,當oasync()被調用後立即超出範圍,並且它將被刪除,成員也將被刪除。

如何正確和很好地解決這個問題(!)?

顯然one solution是通過o到捕獲列表。即使不使用,捕獲也保證不被優化。

async(&o->member, [o] { do_something_else(); }); 

然而,最近的編譯器(鐺-5.0)包括-Wextra集合中的-Wunused-lambda-capture。這種情況下會產生未使用的lambda捕獲警告。

我在lamdba裏面加了(void) o;,它使這個警告無聲無息。

async(&o->member, [o] { 
     (void) o; 
     do_something_else(); 
    }); 

是否有更優雅的方式來解決這個問題的範圍?

(這個問題的起源是從using write_async of boost::asio派生)

+0

是'member'靜態函數取'的std ::功能'?這是我能想到的唯一可能性,在這種情況下,「o」甚至不需要保持活力。 –

+0

假設爲了簡單起見'member'是一個'int'。 –

+0

那麼int *是一個可調用函數? –

回答

0

不是一個偉大的答案,但...

它似乎並不像有一定有「更好」 /「乾淨」的解決方案,儘管我建議更多的「自我描述」解決方案可能是爲顯式綁定成員函數和其中的shared_ptr實例的線程操作創建一個函子。使用虛擬lambda表達式捕獲並不一定能捕捉到這個意圖,有人可能會稍後再來,並將其「優化」到一個糟糕的結局。誠然,綁定函子與shared_ptr的語法稍微複雜一些。我的2c,無論如何(我已經做了類似於我的建議,供參考)。

0

我在a project of mine中使用的解決方案是從enable_shared_from_this派生類並讓它通過存儲所述共享指針的一個拷貝的數據成員的異步呼叫期間泄漏
參見Resource class瞭解更多細節,特別是成員方法leakreset
一旦清理它看起來像下面的小例子:

#include<memory> 

struct S: std::enable_shared_from_this<S> { 
    void leak() { 
     ref = this->shared_from_this(); 
    } 

    void reset() { 
     ref.reset(); 
    } 

private: 
    std::shared_ptr<S> ref; 
}; 

int main() { 
    auto ptr = std::make_shared<S>(); 
    ptr->leak(); 
    // do whatever you want and notify who 
    // is in charge to reset ptr through 
    // ptr->reset(); 
} 

的主要風險是,如果你從來沒有復位內部指針你有一個實際的泄漏。在這種情況下,很容易處理它,因爲底層庫需要資源在放棄之前顯式關閉,並在關閉時重置指針。在此之前,生物資源可以通過適當的功能(walkLoop class的成員函數,仍然是映射到底層庫提供的東西)來檢索,並且仍然可以隨時關閉它們,因此可以完全避免泄漏。
在你的情況下,你必須找到辦法避免這個問題,這可能是一個問題,但它主要取決於實際的代碼,我不能說。
一個可能的缺點是,在這種情況下,你不得不通過共享指針上創建動態存儲你的對象,否則整個事情會爆發並不起作用。

1

我建議你async功能沒有優化設計。如果async將來會在某個任意點調用這個函數,並且它需要當時的connection還活着,那麼我會看到兩種可能性。你可以做任何擁有底層邏輯的東西async也擁有connection。例如:

class task_manager { 
    void async(connection*, std::function<void()> f); 
    connection* get_connection(size_t index); 
}; 

這樣,connection將永遠活着的時候async被調用。 或者,你可以有async採取unique_ptr<connection>shared_ptr<connection>

void async(std::shared_ptr<connection>, std::function<void()> f); 

這比捕捉connection在封閉的主人,可能有無法預料的副作用(包括更好的是async可能期望connection留在函數對象被調用和銷燬之後活着)。

+0

同意,但我不能改變我在現實中使用的異步函數的原型。它是boost :: asio中的一個(參見我的問題結尾處的鏈接)。 –

2

Boost.Asio似乎suggest使用enable_shared_from_this來保留任何擁有「connection」活着,而有未決的操作使用它。例如:

class task : std::enable_shared_from_this<task> { 
public: 
    static std::shared_ptr<task> make() { 
    return std::shared_ptr<task>(new task()); 
    } 

    void schedule() { 
    async(&conn, [t = shared_from_this()]() { t->run(); }); 
    } 

private: 
    task() = default; 

    void run() { 
    // whatever 
    } 

    connection conn; 
}; 

然後使用task

auto t = task::make(); 
t->schedule(); 

這似乎是一個好主意,因爲它封裝了所有調度邏輯和執行task本身內的task

+0

這是用asio做*的方式。你可以使用這種方法保持套接字/客戶端處理程序等活着,並在不再有異步事件處理它們時允許它們優雅地過期。 – Nim

相關問題