2016-09-30 19 views
-2

我正在Windows下創建一個線程,它需要一個參數塊作爲void *(LPVOID)傳遞。通過需要void *參數的函數傳遞unique_ptr的最安全方法是什麼?

調用函數堆分配參數塊,因爲生成的線程可能超過調用函數。調用函數不需要共享參數塊的所有權。

這裏是我的想法至今:

... 
using Functor = std::function<void()> 

class SpawnData 
{ 
public: 
    SpawnData(Functor func) : func(func) {} 
    Functor func; 
}; 

DWORD WINAPI MyTaskLauncher(LPVOID ppRawData) 
{ 
    assert(ppRawData != nullptr); 
    //auto pData = *static_cast<std::unique_ptr<SpawnData>*>(ppRawData); //not permitted (A) 
    auto ppData = static_cast<std::unique_ptr<SpawnData>*>(ppRawData); //Feels safe, but... 
    assert(*ppRawData != nullptr); 
    (*ppRawData)->func(); //could dereference invalid memory? 
} 

ThreadId MyThreadSpawner(Functor func) 
{ 
    auto pData = std::make_unique<SpawnData>(func); 

    //CreateThread() is Windows API with a void* signature for the data param 
    ... = CreateThread(..., static_cast<LPTHREAD_START_ROUTINE>(MyTaskLauncher), 
         static_cast<LPVOID>(&(pData.get())), ...); //Ugh... :(

    threadId = ...; 
    return threadId; 
} 

我的問題:

1)你是否同意有開始調用的CreateThread(比賽條件)MyTaskLauncher重新包裝MyThreadSpawner()之前的原始指針退出?

2)你是否同意Q1)本質上是沒有實際意義,因爲MyThreadSpawner()的pData中會被破壞,當它超出範圍無論MyTaskLauncher已經‘包裝’它的內存與否?

3)通過CreateThread()API去除,傳遞和重新包裝我的智能指針的最安全方法是什麼?

考慮以下幾點:

DWORD WINAPI MyTaskLauncher(LPVOID pRawData) 
{ 
    assert(pRawData != null) 
    auto pData = std::make_unique<SpawnData>(*static_cast<SpawnData*>(pRawData)); 
    //signal MyThreadSpawner() that SpawnData is safely wrapped 
    //...do work... 
    return result; 
} 

ThreadId MyThreadSpawner(Functor func) 
{ 
    auto pData = std::make_unique<SpawnData>(func); 

    //CreateThread() is Windows API with a void* signature for the data param 
    ... = CreateThread(..., static_cast<LPTHREAD_START_ROUTINE>(MyTaskLauncher), 
         static_cast<LPVOID>(pData.get()), ...); //Hmm...   

    threadId = ...; 
    //Wait for MyTaskLauncher to signal SpawnData is safely wrapped 
    return threadId; 
} 

4)這是合法的嗎?一時間,會有兩個,呃,指向同樣的內存unique_ptrs ...

如果我更新此使用的shared_ptr如下:

//MyTaskLauncher: 
    ... 
    auto pData = *static_cast<std::shared_ptr<SpawnData>*>(pRawData)); // (B) 
    ... 

//MyThreadSpawner: 
    auto pData = std::make_shared<SpawnData>(func); 
    ... 

5)的行標(B) ,以上是合法的,而(A)遠高於(與std::unique_ptr<SpawnData>*相同)不是。任何人都可以闡明爲什麼?

6)最後,關於更簡單和/或更安全的技術,通過需要void *的函數簽名傳遞安全數據的任何建議。

在此先感謝您的想法。

+2

沒有「安全」的方式來做到這一點,一旦你轉向'void *',類型系統關閉,&&pData'是堆棧中的一個地址。 – imreal

+2

在我看來,調用者根本不應該使用'unique_ptr'。新的線程可能會使用'unique_ptr',但我認爲調用者最好使用原始指針。 – user2357112

+0

@user這可能是務實的答案,我授予你,但隨着解決方案的老化和維護,我會睡得更好,知道堆分配儘可能得到了最大程度的保護。此外,我覺得像非類​​型安全的API依賴性這樣的障礙應該明確地不會造成放棄代碼中其他地方的最佳實踐的漣漪效應。 – U007D

回答

2

2)你是否同意Q1)本質上是沒有實際意義,因爲MyThreadSpawner()的pData中會被破壞,當它超出範圍,無論是否MyTaskLauncher已經‘包裝’它的內存或不?

是的。這就是unique_ptr;它代表獨有的所有權。你試圖打破這一點。

3)通過CreateThread()API去除,傳遞和重新包裝我的智能指針的最安全方法是什麼?

定義「最安全」。目前尚不清楚爲什麼MyThreadSpawner完全使用unique_ptr

您正在嘗試轉讓unique_ptr的所有權。所以你需要真正做到這一點;發送方必須失去所有權,接收方必須獲得所有權。這是相當簡單:

DWORD WINAPI MyTaskLauncher(LPVOID pData) 
{ 
    assert(pData!= nullptr); 
    //Gain ownership of memory. 
    unique_ptr<SpawnData> pSpawnData(static_cast<SpawnData*>(pData)); 
    pSpawnData->func(); 
} 

ThreadId MyThreadSpawner(Functor func) 
{ 
    auto pData = std::make_unique<SpawnData>(func); 

    //CreateThread() is Windows API with a void* signature for the data param 
    ... = CreateThread(..., static_cast<LPTHREAD_START_ROUTINE>(MyTaskLauncher), 
         //Lose ownership of memory. 
         static_cast<LPVOID>(pData.release()), ...);  
    threadId = ...; 
    return threadId; 
} 

5)的行標(B),上述是合法的,而(A)遠高於(它同樣爲std ::的unique_ptr *),是不是。任何人都可以闡明爲什麼?

法律意義何在? void *並不指向任何類型的shared_ptr。因此將其轉換爲shared_ptr然後訪問它不是合法的C++。

僅僅因爲你的編譯器碰巧讓你這樣做並不合法。

最後,關於簡單和/或更安全的技術,通過需要void *的函數簽名傳遞安全數據的任何建議。

是:使用std::thread。您可以通過thread::native_handle獲得Win32線程句柄。

+0

使用unique_ptr的MyThreadSpawner()背後的意圖通常是:確保它在分配的範圍內(例外)安全地處理它分配的資源,直到所有權被移交或不再需要該對象。謝謝 - 你的回答非常有幫助。 – U007D

相關問題