C++ 取消分配动态分配的内存,然后重新调用的“全部捕获”块是有效/良好的设计选择吗?

C++ 取消分配动态分配的内存,然后重新调用的“全部捕获”块是有效/良好的设计选择吗?,c++,exception,C++,Exception,我通常使用现代C++的功能,如智能指针,很少使用原始指针作为动态分配对象的处理程序。因此,我没有太多的交易经验。我想知道下面的代码示例是否是防止异常引发的内存泄漏的有效设计选择: void HttpListener::spawnRequestHandler(const http_request& request) { std::thread handlerThread([request](){ IRequestHandler* handler = new Http

我通常使用现代C++的功能,如智能指针,很少使用原始指针作为动态分配对象的处理程序。因此,我没有太多的交易经验。我想知道下面的代码示例是否是防止异常引发的内存泄漏的有效设计选择:

void HttpListener::spawnRequestHandler(const http_request& request) {
    std::thread handlerThread([request](){
        IRequestHandler* handler = new HttpRequestHandler(request);
        try {
            handler->handleRequest();
        }
        catch (...){
            delete handler;
            std::rethrow_exception(std::current_exception());
        }
    });
    handlerThread.detach();
}

我建议也将内存分配放入try块,并更具体地捕获哪些异常,例如

IRequestHandler* handler = nullptr;
try {
    handler = new HttpRequestHandler(request);
    handler->handleRequest();
}
catch(const std::bad_alloc& e) {
    log("Not enough heap memory."); //however you log or use cout
}
catch(const HttpRequestHandlerExeption1& e) {
    delete handler;
    HttpRequestHandlerstd::rethrow_exception(std::current_exception());
}
.
.
.
delete handler;

一篇关于捕获多个异常的好文章你可以找到

我建议也将内存分配放在try块中,并更具体地捕获哪些异常,例如

IRequestHandler* handler = nullptr;
try {
    handler = new HttpRequestHandler(request);
    handler->handleRequest();
}
catch(const std::bad_alloc& e) {
    log("Not enough heap memory."); //however you log or use cout
}
catch(const HttpRequestHandlerExeption1& e) {
    delete handler;
    HttpRequestHandlerstd::rethrow_exception(std::current_exception());
}
.
.
.
delete handler;

一篇关于捕获多个异常的好文章,你可以找到你建议的方法,但是我建议不要这样做。 C++是非常少的语言,它具有RAII的概念,它是为了执行异常安全代码而创建的。 简而言之,如果你有一个调用,它会获取/创建拥有它的构造函数,并在它的析构函数中进行清理

一些非常好的例子是str::unique_ptr和std::scoped_lock。但是,如果您有多个return语句,这也非常有用

在这种情况下,我将调整代码以:

void HttpListener::spawnRequestHandler(const http_request& request) {
    std::thread handlerThread([request](){
        auto handler = std::make_unique<HttpRequestHandler>(request);
        handler->handleRequest();
        handler.release();
    });
handlerThread.detach();
}
正如您所看到的,代码更小,更易于阅读。并且您可以在所有操作都正确结束后通过发布停止删除。不确定这在您的原始代码中是否是一个大问题。不过,如果有意的话,这会使它显式化,之后更容易调试

如果您需要其他需要在销毁时执行的操作,这可能非常有用。不过,安德烈·亚历山德雷斯库(Andrei Alexandrescu)所作的这篇演讲所依据的链接并不确定,它拥有你能想象到的所有功能


注意:如果您不需要该版本,您也可以在堆栈上创建实例。

您的建议将起作用,但我建议不要这样做。 C++是非常少的语言,它具有RAII的概念,它是为了执行异常安全代码而创建的。 简而言之,如果你有一个调用,它会获取/创建拥有它的构造函数,并在它的析构函数中进行清理

一些非常好的例子是str::unique_ptr和std::scoped_lock。但是,如果您有多个return语句,这也非常有用

在这种情况下,我将调整代码以:

void HttpListener::spawnRequestHandler(const http_request& request) {
    std::thread handlerThread([request](){
        auto handler = std::make_unique<HttpRequestHandler>(request);
        handler->handleRequest();
        handler.release();
    });
handlerThread.detach();
}
正如您所看到的,代码更小,更易于阅读。并且您可以在所有操作都正确结束后通过发布停止删除。不确定这在您的原始代码中是否是一个大问题。不过,如果有意的话,这会使它显式化,之后更容易调试

如果您需要其他需要在销毁时执行的操作,这可能非常有用。不过,安德烈·亚历山德雷斯库(Andrei Alexandrescu)所作的这篇演讲所依据的链接并不确定,它拥有你能想象到的所有功能

注意:如果您不需要该版本,您也可以在堆栈上创建实例。

这里已经有一个实例

然而,代码似乎有更多的问题。应该在哪里清理请求处理程序原始指针

可能它不太可能被http_请求拥有,或者在Handlerequest中删除自身。同样,我们也不太可能从示例中看到这一点,但这两者都是不好的做法。看起来像是内存泄漏

此外,无需显式使用接口IRequestHandler

总之,线程中的代码可以简化为:

HttpRequestHandler handler(request);
handler.handleRequest();
此外,您不需要指向基类的指针,也可以使用引用:

IRequestHandler& handlerInterface = handler;
这里已经有一个

然而,代码似乎有更多的问题。应该在哪里清理请求处理程序原始指针

可能它不太可能被http_请求拥有,或者在Handlerequest中删除自身。同样,我们也不太可能从示例中看到这一点,但这两者都是不好的做法。看起来像是内存泄漏

此外,无需显式使用接口IRequestHandler

总之,线程中的代码可以简化为:

HttpRequestHandler handler(request);
handler.handleRequest();
此外,您不需要指向基类的指针,也可以使用引用:

IRequestHandler& handlerInterface = handler;

使用unique_ptr而不是裸新,unique_ptr将在超出范围时释放其指针,即使是在出现异常的情况下。@jkb在我看来,他似乎已经知道unique_ptr技术,特别是在处理原始指针时应该做些什么。我通常使用现代C++的功能,比如智能指针——为什么不在这里呢?这将是智能指针的一个很好的使用案例。@K.R.Park智能指针将把lambda函数减少到只有两行。在这种情况下没有理由使用原始指针。我认为他的重点是了解背景。因此,从纯教育的角度来看,使用原始指针对我来说是有意义的。使用unique_ptr而不是裸新,unique_ptr在运行时将释放其指针
范围t,即使是在异常情况下。@jkb在我看来,他已经意识到了独特的_ptr技术,并特别询问在处理原始指针时应该做什么。我通常使用现代C++的功能,如智能指针-为什么不在这里?这将是智能指针的一个很好的使用案例。@K.R.Park智能指针将把lambda函数减少到只有两行。在这种情况下没有理由使用原始指针。我认为他的重点是了解背景。因此,从纯教育的角度来看,使用原始指针对我来说是有意义的。这种解决方案肯定更好,但是我故意避免使用智能指针,因为我觉得我需要使用原始指针来使用动态多态性。我忘记了std::dynamic_pointer_cast,所以我想我将使用共享指针进行上转换,您不需要原始指针。即使您需要一个原始指针,您仍然可以将所有权存储在智能指针中,并检索原始指针进行传递。简单提示:不能省略ownership.handler.release的原始指针。@stefan.gal查看原始代码,如果您想保留行为,则不能省略。这个解决方案肯定更好,这可能是一个错误,但是我故意避免使用智能指针,因为我觉得我需要使用原始指针来使用动态多态性。我忘记了std::dynamic_pointer_cast,所以我想我将使用共享指针进行上转换,您不需要原始指针。即使您需要一个原始指针,您仍然可以将所有权存储在智能指针中,并检索原始指针进行传递。简单提示:不能省略ownership.handler.release的原始指针。@stefan.gal查看原始代码,如果您想保留行为,则不能省略。这可能是个错误。非常感谢你的回答。我实际上忘记清理处理程序指针了。我最初使用动态分配对象的原因是因为我仍然认为在堆栈上分配的对象总是在函数块结束后被销毁,而不是考虑到在这种情况下,堆栈将位于另一个线程中,并且不受关闭spawnRequestHandler函数块的影响。非常感谢您的回答。我实际上忘记清理处理程序指针了。我最初使用动态分配对象的原因是,我仍然认为在堆栈上分配的对象总是在函数块结束后被销毁,没有考虑到在这种情况下,堆栈将位于另一个线程中,并且不会受到关闭spawnRequestHandler函数块的影响。