std::shared_ptr:自定义删除器未被调用

3
我正在学习《C++ Primer第五版》的内容,本书中作者提供了一个例子,用于使用shared_ptr管理资源以避免内存泄漏。我决定创建一个测试来了解其工作原理。但是,在异常抛出且未被捕获时,我的自定义删除器并没有被调用:
#include <iostream>
#include <memory>
#include <string>

struct Connection {};

Connection* Connect(std::string host)
{
    std::cout << "Connecting to " << host << std::endl;
    return new Connection;
}

void Disconnect(Connection* connection)
{
    std::cout << "Disconnected" << std::endl;
    delete connection;
}

void EndConnection(Connection* connection)
{
    std::cerr << "Calling disconnect." << std::endl << std::flush;
    Disconnect(connection);
}

void AttemptLeak()
{
    Connection* c = Connect("www.google.co.uk");
    std::shared_ptr<Connection> connection(c, EndConnection);

    // Intentionally let the exception bubble up.
    throw;
}

int main()
{
    AttemptLeak();

    return 0;
}

它会产生以下输出:

连接到www.google.co.uk

我的理解是,当函数退出时,无论是正常退出还是因异常而退出,所有局部变量都将被销毁。在这种情况下,应该意味着在AttemptLeaks()退出时销毁connection,调用其析构函数,然后应该调用EndConnection()。请注意,我正在使用和刷新cerr,但也没有输出任何内容。
我的示例有问题,还是我的理解有误? 编辑:虽然我已经得到了这个问题的答案,但对于未来遇到这个问题的其他人来说,我的问题在于我对throw的工作原理的理解错误。尽管以下答案正确地说明了如何使用它,但我认为最好明确地表明,我(错误地)试图使用它来“生成”未处理的异常,以测试上面的代码。

始终要在某个地方捕获异常。 - Mooing Duck
@MooingDuck 我知道。我是_有意地_不捕捉它,因为问题陈述中指出要看它的表现,但它并没有像作者说的那样行为。 - John H
1
正如下面的答案所述,您对shared_ptr和析构函数有正确的理解,您对异常的理解错误。 - Mooing Duck
@MooingDuck 感谢您的确认,我非常感激您的帮助。 - John H
2个回答

8

throw语句通常在catch块中用于重新抛出捕获的异常。如果在catch块之外使用,将会调用terminate()函数并导致程序立即结束。详情请参见在catch块之外使用“throw;”会发生什么?

如果删除throw语句,shared_ptr类型的connection将超出作用域并调用其析构器。如果您对使用shared_ptr的异常安全性存在疑虑(我没有;),您可以通过将throw更改为throw 1来明确地抛出异常。


1
@JohnH:我觉得你不理解。throw关键字单独使用是在catch块内部用于重新抛出捕获的异常。而你这里并没有东西可供重新抛出。 - Fred Larson
1
你的代码中没有catch,而terminate()会突然结束程序,而不会抛出任何异常。请阅读参考的Stack Overflow问题“在catch块外部使用throw关键字的含义”。 - Peter G.
1
啊,我现在从链接的答案中明白你的意思了。如果在异常未激活时执行If throw;,它会调用terminate()(§15.1/8)。可能就是这个原因。 - John H
1
@PeterG。关于当找不到处理程序时会发生什么(§15.3/9):“如果找不到匹配的处理程序,则调用函数std::terminate();在调用std::terminate()之前是否展开堆栈是由实现定义的”。因此,您无法确定任何一种情况(除非阅读实现的文档)。 - James Kanze
1
@PeterG。我认为“没有匹配的处理程序情况”不适用于在没有活动异常时遇到的抛出。相关章节是§15.1/9和§15.5.1/2。在这种情况下,堆栈展开将永远不会发生。 - Praetorian
显示剩余3条评论

3
没有操作数的 throw 表达式是用于重新抛出当前正在处理的异常。如果没有正在处理的异常,那么会调用 std::terminate 函数。在这种情况下,不会进行堆栈展开(unwinding),这就是为什么析构器从未被调用的原因。请将代码更改为以下内容:
void AttemptLeak()
{
    Connection* c = Connect("www.google.co.uk");
    std::shared_ptr<Connection> connection(c, EndConnection);

    // Intentionally let the exception bubble up.
    throw 42; // or preferably something defined in <stdexcept>
}

int main()
{
    try {
        AttemptLeak();
    } catch(...) {
    }
    return 0;
}

现在当shared_ptr超出作用域时,删除器将被调用。

谢谢你提供的对Peter答案的替代方案。这个也可以。 - John H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接