Search code examples
c++c++11smart-pointers

Smart Pointers and Exception


A practice question on C++ primer:

one easy way to make sure resources are freed is to use smart pointers.

Imagine we're using a network library that is used by both C and C++. Programs that use this library might contain code such as:

struct connection
{
    string ip;
    int port;
    connection(string i, int p) :ip(i), port(p) {};
};  // represents what we are connecting to

struct destination
{
    string ip;
    int port;
    destination(string i, int p) :ip(i), port(p) {};
};  // information needed to use the connection

void disconnect(connection c)
{
    cout << "Scoket(" << c.port << "," << c.port << ") disconnect" << endl;
}  //close the given connection

connection connect(destination* pDest)
{
    shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port), disconnect );
    cout << "Scoket(" << pConn->ip << "," << pConn->port << ") connect" << endl;
    return *pConn;
}  // open the connection


void f(destination& d)
{
    connection c = connect(&d);
}

compile error:

error C2661: 'std::shared_ptr<connection>::shared_ptr': no overloaded function takes 2 arguments.

What's wrong with the code?


Solution

  • From cppreference, there is indeed an overload that takes two arguments, but it doesn't work the way you want. You're looking at overload (4).

    template< class Y, class Deleter >
    shared_ptr( Y* ptr, Deleter d );
    

    Uses the specified deleter d as the deleter. The expression d(ptr) must be well formed, have well-defined behavior and not throw any exceptions. The construction of d and of the stored deleter copied from it must not throw exceptions.

    (emphasis mine)

    Note that the deleter must be a callable object which accepts an argument of type Y*, i.e. a pointer to the data within the std::shared_ptr. So your disconnect function, if you intend to use it as a deleter for a std::shared_ptr, should have signature

    void disconnect(connection* c)
    

    with a pointer argument.

    Also note that your shared pointer, as it's written, is already very likely to exhibit undefined behavior.

    connection connect(destination* pDest)
    {
        shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port), disconnect );
        cout << "Scoket(" << pConn->ip << "," << pConn->port << ") connect" << endl;
        return *pConn;
    }
    

    Let's break down what you've done here. First, you create a shared pointer to a new connection object. That connection object is owned and managed by the std::shared_ptr. When there are no more std::shared_ptr objects pointing to that memory, it will be deallocated, and your deleter will run. Then you return (a copy of) the underlying connection. The std::shared_ptr is a local variable and is freed as a matter of course, and that was the only shared pointer to the connection* you allocated. Hence, disconnect shall be called. As soon as connect returns, it returns a copy of a connection which has already been closed. If someone tries to use the connection object, it will fail, or worse, exhibit undefined behavior.

    If you're going to use smart pointers, you have to commit to it. Your connect function must return a std::shared_ptr<connection>, which the caller then manages. Constructing a shared pointer does no good if all you return is a raw pointer.