3

I'm working through C++ Primer, 5th edition, and the author has presented an example to do with using shared_ptrs to manage resources from older libraries that could leak memory, to prevent them from doing so. I decided to create a test to see how it works, but my custom deleter doesn't get called after the exception is thrown and (deliberately) not caught:

#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;
}

It produces the following output:

Connecting to www.google.co.uk

My understanding is that when a function is exited, whether that's exiting normally or because of an exception, the local variables will all be destroyed. In this case, that should mean connection being destroyed when AttemptLeaks() exits, invoking its destructor, which should then call EndConnection(). Notice also that I'm using, and flushing, cerr, but that also didn't give any output.

Is there something wrong with my example, or my understanding?

Edit: While I already have the answer to this question, for anyone else that stumbles upon this in the future, my problem was with my understanding of how throw works. Although the answers below correctly state how to use it, I think it's best to explicitly make it clear that I was (incorrectly) trying to use it to 'generate' an unhandled exception, to test my code above.

John H
  • 14,422
  • 4
  • 41
  • 74
  • Always catch exceptions _somewhere_ – Mooing Duck Mar 11 '14 at 17:14
  • @MooingDuck I know. I'm _intentionally_ not catching it, as the question states, to see _how_ it behaves, and it's not behaving how the author says it should. – John H Mar 11 '14 at 17:15
  • 1
    as the answer below states, you have a correct understanding of shared_ptr and destructors, the error in your understanding is that of _exceptions_. – Mooing Duck Mar 11 '14 at 17:17
  • @MooingDuck Thanks for verifying my understanding. I appreciate the help. – John H Mar 11 '14 at 17:25

2 Answers2

8

Bare throw is intended for use inside catch blocks to rethrow a caught exception. If you use it outside a catch block, terminate() will be called and your program ends at once. See what does "throw;" outside a catch block do?

If you delete the throw-statement the shared_ptr connection will go out of scope and should call the deleter. If you have any doubts about the exception-safety of using a shared_ptr (I don't ;), you can explicitly throw an exception here by changing throw to throw 1.

Community
  • 1
  • 1
Peter G.
  • 14,786
  • 7
  • 57
  • 75
  • I understand it's to be used with `catch` blocks. The example presented in the book is saying that by specifying a custom deleter, like in this example, I can have that memory cleaned up automatically in case an unhandled exception occurs between when the memory is allocated and when it should be deleted. – John H Mar 11 '14 at 17:14
  • 1
    @JohnH: I don't think you understand. `throw` by itself is used WITHIN a `catch` block to RE-throw the caught exception. You have nothing here to re-throw. – Fred Larson Mar 11 '14 at 17:16
  • 1
    There is no `catch` in your code and `terminate()` ends the program abruptly without an expection being thrown at all. Please read the referenced stack overflow question "what does 'throw' outside a catch block do". – Peter G. Mar 11 '14 at 17:16
  • 1
    Ah, I see what you mean from the linked answer now. `If throw; is executed when an exception is not active, it calls terminate() (§15.1/8).` That might be it. – John H Mar 11 '14 at 17:17
  • I already knew it worked correctly when `throw` wasn't specified, but I wanted to specifically test this when an exception occurred, just so I could see it for myself. I just tried stuffing a random function call in `AttemptLeak()`, which could throw an exception, and made it do so. After catching that exception, the deleter was invoked properly. Thanks for your time. – John H Mar 11 '14 at 17:24
  • 1
    @PeterG. Concerning what happens when no handler is found (§15.3/9): "If no matching handler is found, the function std::terminate() is called; whether or not the stack is unwound before this call to std::terminate() is implementation-defined". So you can't make any bets one way or the other (except by reading the documentation of the implementation). – James Kanze Mar 11 '14 at 17:26
  • 1
    @PeterG. I don't think the "no matching handler case" applies to a throw encountered when there is no active exception. The relevant sections are §15.1/9 and §15.5.1/2. Stack unwinding will *never* take place in this situation. – Praetorian Mar 11 '14 at 17:40
  • @Praetorian I hadn't noticed that he hadn't provided an argument to the throw. In that case, `std::terminate` _will_ be called before stack unwinding. Or rather, there won't be any stack unwinding, because the `throw` itself doesn't take place. – James Kanze Mar 11 '14 at 18:00
3

The throw expression without an operand is intended for rethrowing the exception being currently handled. If no exception is being handled then std::terminate is called. In this situation stack unwinding does not take place, which is why the deleter is never being called. Change your code to the folowing:

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;
}

Now the deleter will be called when the shared_ptr goes out of scope.

Praetorian
  • 106,671
  • 19
  • 240
  • 328