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?
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 expressiond(ptr)
must be well formed, have well-defined behavior and not throw any exceptions. The construction ofd
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.