2016-09-30 39 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

相关问题