I was only thinking about appropriate semantics for managing the
memory. If the object pointed to is responsible for a resource of its
own too (a socket connection) then it too needs to have been written
with RAII (or rather, the corollary of RAII, Destruction is Resource
Release, which is usually the real point of RAII anyway) in mind, but
that's an orthogonal point.
I don't think so. A socket has (or probably should have) a
specific lifetime; it's lifetime is not something that you can
somehow "automate", because it depends on external events.
This isn't necessarily true, however, and I can think of
scenarios, where a socket would have a lifetime which
corresponds to scope. But that isn't the case here, since the
poster is intentionally allocating them.
There is a second potential problem with regards to using RAII
with a socket: releasing the resource can fail, and you may have
to handle the error. In practice, this would mean that you
cannot release the resource in the destructor. Again, this
depends somewhat on design.
RAII, in its simplest form, works best for objects whose
lifetime depends on scope. Things like shared_ptr can be used
for memory managment, but aren't really applicable for objects
with an explicit lifetime. And of course, today, if you're
starting a new project, the only reasonable course of action is
to use the Boehm collector if you can. So shared_ptr becomes
really only pertinant to legacy applications which can't use the
Boehm collector. (I'm speaking here of the boost::shared_ptr
using the default deleter. There are some special scenarios
where it is still very, very useful with a user defined deleter
object.)
... via the interface of the class that's responsible for a socket
connection.
I was thinking at a higher level. When does the lifetime end?
As a result of what criteria.
In the servers I currently work on, for example, the socket
connection's lifetime ends when the client logs out; an external
event. This is typical of a lot of TCP servers, I think. In a
client, it probably depends on what the connection is being used
for; a service connection used to obtain a specific bit of
information, then closed, will probably obey scope; a more or
less permanent connection used within a library will have an
explicit lifetime, with client code calling a specific function
to terminate its lifetime. (Certainly, the class which is
responsible for the socket will not want to listen on a specific
button to know when to close the socket.)
I don't understand the need for the loop before the call to erase. The
objects in the container (smart pointers) do that for you.
The whole point is that it is probably bad design to use smart
pointers for such objects. Most of the time, I imagine that the
socket object itself, or a decorator, will be aware of the
vector, and remove the object from the vector in its destructor.
In such cases, the above loop will *NOT* work; the correct logic
is:
while ( ! m_collClients.empty() ) {
m_collClients.back().requestShutdown() ;
}
If the container is being managed separately, however, the above
loop is a valid solution. Again, however, mainly in the case of
a clean shutdown; most of the time, the socket objects will be
removed from the collection by whoever is managing their
lifetime, e.g. when the other end disconnects, or the user
requests closing a specific connection.
I wasn't thinking about object lifetimes. I was thinking about
exception safety and robustness in the face of changes to the code.
Any changes to code have to respect the design. If the object
lifetime should be deterministic, trying to use shared_ptr won't
change that, and is more error prone than handling the
conditions explicitly.
What do you mean by "dead object", how would I end up with one hanging
around, and (if it's not obvious from the definition of "dead object")
why would that be bad?
A dead object is one that still "exists", but shouldn't,
according to the program logic. It's potentially a problem with
all types of garbage collection, including boost::shared_ptr.
With normal garbage collection, however, you expect it; you
don't use the destructor to "invalidate" the object, but a
special member function, which marks the object as invalid. An
assertion check at the start of all functions then detects any
invalid use. With shared_ptr, there is a tendancy to count on
the destructor, which results in the object not being
invalidated when it should be, because someone still has a
pointer to it hanging around.
I've often felt that in the case of such objects with specific
lifetime, you really need some sort of "reverse garbage
collection"; something that would get rid of pointers to the
object whenever the object dies. I once had (and in fact still
have) a managed pointer which will null when the destructor of
the pointed to object is called. (This can be simulated with
boost::shared_ptr by having the object itself contain the only
shared_ptr, and all other objects use weak_ptr. Personally, I
find this a bit of obfuscation---setting the local shared_ptr to
null is a very unobvious way of writing "delete this".) In
practice, I've almost never used it, since it's usually
necessary to do more than just set the pointers to null, and
some other notification mechanism is required anyway.