我有一个关于异常生命周期的可移植性问题。在下面的代码中,在一个线程(mySlave)中抛出异常并使用std::exception_ptr
转移到另一个线程(myMaster)。 myMaster始终通过std::condition_variable
等待不同的事件。 mySlave中的一个例外是这样的事件。在myMaster中wait的谓词函数中,我检查异常指针是否为null。如果在mySlave中抛出异常,我将异常指针复制到myMaster中的临时变量,将原始异常指针设置为null并在myMaster中重新抛出它。这样,一旦程序从异常中恢复,原始异常指针就可以在谓词函数中提供。
这适用于VC14,但最终软件可能会在未来移植到其他平台。在我的代码中,对异常的所有exception_ptr
引用都将在重新抛出后超出范围,因此原始异常将被销毁。我担心的是,std::rethrow_exception
是否可以保证在重新抛出异常时始终生成异常的副本,或者它是否也可以使用对异常的引用,当我尝试在myMaster中捕获时,它将不再有效?
#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,但我认为这可能会在以后添加新异常并且程序员忘记将异常指针置空时引入错误。
修改
我在这个问题上找到了以下陈述:
std :: exception_ptr引用的异常对象仍然有效 只要至少有一个std :: exception_ptr 引用它
rethrow_exception的VS实现似乎是一个副本 例外。 Clang和gcc不会复制。
异常对象在最后一个剩余之后被销毁 异常的活动处理程序以除以外的任何方式退出 重新抛出,或std :: exception_ptr(第18.8.5节)类型的最后一个对象 引用异常对象被销毁,以较晚者为准。
从(1)我可以预期异常会被过早销毁。从(2)我可以预期,无论如何使用VC作为副本时这都没有效果。从3开始,我无法分辨这是否可以在使用gcc或clang时拯救我。我的意思是,退出是通过重新抛出来实现的,但它并不是从处理程序中重新抛出的。当临时指针被销毁时,新的处理程序是否已经被认为是活动的,或者指针是否先被销毁并且异常,并且后续的catch块具有无效的异常引用?