【发布时间】:2017-10-03 15:08:57
【问题描述】:
我有一个关于异常生命周期的可移植性问题。在下面的代码中,在一个线程 (mySlave) 中引发异常并使用 std::exception_ptr 将异常转移到另一个线程 (myMaster)。 myMaster 总是通过std::condition_variable 等待不同的事件。 mySlave 中的一个例外就是这样的事件。在 myMaster 的 wait 的谓词函数中,我检查异常指针是否为空。如果在 mySlave 中抛出了异常,我将异常指针复制到 myMaster 中的一个临时变量,将原始异常指针设置为 null 并在 myMaster 中重新抛出它。这样,一旦程序从异常中恢复,原始异常指针就可以在谓词函数中服务了。
这在 VC14 上运行良好,但最终的软件很可能在未来被移植到其他平台。在我的代码中,所有对异常的exception_ptr 引用将在重新抛出后超出范围,因此原始异常将被销毁。我担心std::rethrow_exception 是否保证在重新抛出异常时始终生成异常的副本,或者它是否也可以使用对异常的引用,导致当我尝试在 myMaster 中捕获 if 时它不再有效?
#include <mutex>
#include <thread>
#include <atomic>
#include <exception>
#include <iostream>
#include <memory>
class SomeClass
{
public:
/*...*/
void MaseterFunction();
void SlaveFunction();
private:
/*...*/
std::mutex mutex_gotEvent;
std::condition_variable condVar_gotEvent;
std::exception_ptr slaveLoopException;
/*...*/
std::atomic<bool> running = true;
};
class MyException : public std::runtime_error
{
public:
MyException() : std::runtime_error("Ooops") {}
};
void SomeClass::SlaveFunction()
{
try
{
throw MyException();
}catch(const std::exception& e)
{
std::unique_lock<std::mutex> lock(mutex_gotEvent);
slaveLoopException = std::current_exception();
condVar_gotEvent.notify_all();
}
}
void SomeClass::MaseterFunction()
{
while (running)
{
try
{
{
/*Wait for something interesting to happen*/
std::unique_lock<std::mutex> lock(mutex_gotEvent);
condVar_gotEvent.wait(lock, [=]()->bool {
return !(slaveLoopException == nullptr); // Real code waits for several events
});
}
/*Care for events*/
/*...*/
if (slaveLoopException)
{
std::exception_ptr temp_ptr = slaveLoopException;
slaveLoopException = nullptr;
std::rethrow_exception(temp_ptr);
}
}
catch (const MyException& e)
{
std::cout << e.what();
running = false;
}
}
}
int main()
{
std::shared_ptr<SomeClass> someClass = std::make_shared<SomeClass>();
std::thread myMaster([someClass]() {someClass->MaseterFunction(); });
std::thread mySlave([someClass]() {someClass->SlaveFunction(); });
std::cin.ignore();
if (myMaster.joinable())
{
myMaster.join();
}
if (mySlave.joinable())
{
mySlave.join();
}
return 0;
}
我考虑过在类级别声明temp_ptr 或使用std::atomic<bool> 变量以及要在谓词函数中使用的异常指针。然而,这两种解决方案都会在不再使用异常后保持异常状态,这对我来说似乎不是很优雅。也可以在 myMaster 中的每个 catch 块中将异常指针设置为 null,但我认为这可能会在以后添加新异常并且程序员忘记将异常指针为 null 时引入错误。
编辑:
我找到了关于这个主题的以下陈述:
std::exception_ptr 引用的异常对象仍然有效 只要还有至少一个 std::exception_ptr 是 引用它
rethrow_exception 的 VS 实现似乎复制了 例外。 Clang 和 gcc 不会复制。
异常对象在最后一个剩余之后被销毁 异常的活动处理程序以任何方式退出 重新抛出,或 std::exception_ptr 类型的最后一个对象(第 18.8.5 节) 指异常对象被销毁,以较晚者为准。
从 (1) 开始,我预计异常会过早地被销毁。从 (2) 开始,我希望这在使用 VC 作为副本时无效。从 3 开始,我不知道这是否可以在使用 gcc 或 clang 时拯救我。我的意思是,退出是通过重新抛出来实现的,但它不是从处理程序中重新抛出的。当临时指针被破坏时,新的处理程序是否已经被认为是活动的,或者指针首先被破坏并伴随着异常,从而使后续 catch 块具有无效的异常引用?
【问题讨论】:
标签: c++ c++11 exception-handling language-lawyer