All of the above allow you to delete the object explicitly.
Do they? boost::shared_ptr certainly doesn't. And the other
two aren't legal in containers: you can't have a map< IdType,
std::auto_ptr >, for example.
IIUC, you will be able to have a map< IdType, std::unique_ptr >
in the next release of the standard. I still feel that most of
the time, the logical and intuitive way of handling the issue is
the reverse: you remove the object from the map in its
destructor, rather than removing it from the map in order to
destruct it. Not to mention the complications involved when
transactional semantics apply: in case of a roll-back, you have
to "undelete" the object (which of course means that you can't
actually have deleted it, in the C++ sense---you've just put a
pointer to it in a buffer in the transaction, and the
transaction will delete it in the commit).
That is almost certainly a required feature of any smart
pointer that is involved with resource ownership.
Apparently, the people who developed the boost::smart_ptr don't
agree with you. (FWIW, my reference counted pointer doesn't
support this either.)
That's certainly not true. There are numerous reasons to
dynamically allocate something that is still tied to some kind
of scope.
Certainly. Polymorphism comes immediately to mind. But are
they that frequent?
void do_something_with_chars(??)
{
std::vector<char> buffer(some calculated size);
... do stuff ...
This is better than using new to create an array because "...
do stuff ..." could be exception generating code. It's just
plain easier to write exception safe code with RAII and, in
fact, sometimes it's the only way.
I totally agree, but are you saying that std::vector is a smart
pointer?
Perhaps I should have been clearer: at the application level, I
find it fairly rare to dynamically allocate objects with a
lifetime which corresponds to scope. I don't consider using
std::vector or std::string dynamically allocating objects at the
application level---what goes on behind the scenes doesn't
interest me (provided they works, and the profiler doesn't say I
have to take an interest). When I think "smart pointer" related
to memory or lifetime management, I think of something like
boost::shared_ptr, where the client code does the actual
allocation.
Not in my experience. You certainly don't use it naively but
I've seen no reason to believe a gc would do the job better.
For one thing a gc isn't often deterministic to the
application code
Nor is boost::shared_ptr, unless you're keeping track of all
those pointers yourself. Which sort of defeats the purpose of
it.
As a poor man's garbage collection, used in specific cases where
it is known that no cycles can occur, it actually works pretty
well. But true garbage collection requires even less programmer
effort in such cases, and is generally faster. (When it's
available. I've used the Boehm collector, but I won't pretend
that installing it on an exotic platform is something for a
beginner.)
and thus RAII isn't even possible (and RAII has many uses
beyond working with memory resources, it can be used to guard
any allocatable resource).
Certainly. And for a lot of resources (mutex locks, open file
descriptors, etc.), it's often appropriate---most of the time
(but not always), such resources either have a lifetime which
does correspond to a local scope, or (at least in the case of
open file descriptors) have a lifetime which corresponds to some
other, "managed" object. But in such cases: why the pointer? I
wouldn't consider something like boost::scoped_lock a smart
pointer, for example.
Yes I forgot about that one. I mentioned a couple exceptions
but that wasn't in the list. There is of course a way around
even this though and in many cases is the better answer: null
object.
Whether it is a better answer depends a lot on the situation.
I've used null objects at times, but they're often more work to
implement and less transparent to the reader than a null pointer
and a test.
OK. I was thinking of the most frequent use of pointers in my
code: for navigation. If the data structure is at all dynamic,
the pointers come and go, and change. And since it's such a
frequent use, I'd question your use of "only" in the above; is
it really appropriate to say "the only case" when one of the
cases covers 90% of the uses?
That's sort of vague, but it does sound like something I've no
experience in---the only time I wrote software for a desktop was
when I implemented a GUI front-end; it certainly wasn't a
stand-alone application. (It was also in Java, but off hand: I
don't seem many cases where I'd use things like shared_ptr, but
unlike the other applications I've worked on, I can see
arguments both ways about that. Almost everything is in a
cycle, but there is a very distinct containment hierarchy which
should make it absolutely clear where you need weak_ptr.)
Even when pointers are used "only for navigation" it is of
benefit to use something like a shared_ptr.
This is where I disagree. If you want to use shared_ptr, you
need to manage some sort of zombie state. And in the absence of
any strict hierarchy (like the containment hierarchy of a GUI),
handling cycles is far too error prone. (And cycles there
are. Lot's of them.)
The problem with pointers that are "only for navigation" is
that the objects that have them are dependent upon the
lifetime of other objects and thus are intimately coupled with
them...but they often don't even know about them.
You've confused me a little with too many "they". The object
which contains the pointer certainly knows that it has the
pointer. And in practice, the pointed to object has to know as
well. I've got a smart pointer which manages this on my
(inaccessible) site---sort of a weak_ptr, but without the
shared_ptr. But in practice, I've almost never used it, because
not only does the pointer need to know when the pointed to
object is destructed, but the object which contains the pointer
needs to know as well (e.g. to switch to a back-up resource, or
an alternative strategy, or to remove the pointer from a map or
a set or something). In the end, you almost always need some
variant of the observer pattern.
(When I first started C++, there was a big fad for "relationship
managemend", some sort of generic solution to this
problem---which is very real. The fad passed on, without any
real working solution being found, at least to my knowledge.)
I do sometimes ignore this problem but I often regret that
choice. A shared_ptr is not usually an expense that
overweights the expense of debugging a dangling pointer.
The problem is that the way the shared_ptr prevents the dangling
pointer is by violating the basic premises of the system---that
the object is deleted when the external event says it has to be
deleted. You could probably use a shared_ptr in the object
itself (pointing to the object, and reset by the object when it
received the event terminating its lifetime), and weak_ptr
everywhere else, but that really comes down to my ManagedPtr,
and as I said, I've not found it anywhere near as useful as I'd
hoped.
IMHO, the dangling pointer isn't the problem; it's a symptom of
a larger problem. The real problem is that object A, which
needs object B, wasn't informed that object B ceased to exist,
and so wasn't able to take appropriate action. And avoiding the
dangle will often only serve to hide this larger problem, or
make it harder to find. I regularly replace the global operator
new with a version which overwrites all of the freed memory with
something like 0xDEADBEEF; unless the memory has been
reallocated since the delete, any access through the dangling
pointer is going to cause a core dump, very quickly. (If the
object is polymorphic, immediately, since the vptr was
overwritten. And one of the things I like about garbage
collection in this scenario is that it ensures that the memory
of the destructed object will not be reallocated as long as
there is a pointer to it.)
There are of course times when you actually would prefer a
dangling pointer to an object outliving what is conceived as
its useful lifetime.
Let's formulate that a little differently: are there times whey
you wouldn't prefer a dangling pointer over using an object
whose defined lifetime has ended?
Either way you look at it you probably have a bug on your
hands when an object that has a "navigation only" pointer
exists longer than the object owning the thing it points to.
Exactly.
However, often times the path to recreate this bug is
difficult to recreate and if just having the thing do weird
stuff the user never sees is preferable to a crash.
OK. Maybe that's where our applications differ. Having the
application do "weird stuff" is generally not an option in the
applications I've written. In most cases, it's better for the
application to crash, immediately, so the backup systems (in the
current case, doing the operations manually, or at least knowing
that the system is not performing correctly, and working around
it) can take over.
And designs change. It's easier to say, "Now I want this
object to live longer," if the pointers it's using are shared.
Having the object live longer usually means a major design
change, at least for the objects I have to deal with.
Further, you actually have ways to have a "navigation only"
pointer that can be checked before you access it. In the case
of a raw pointer you have to trust that it is either there, or
has been set to NULL. If both sides are using the shared_ptr
system, one the owner (having a shared_ptr) and the other the
referrer (having a weak_ptr) then when the owner goes away we
know that everyone having a "navigation only" pointer to
whatever parts of it it's shared know the resource is gone.
Yes. That's what my ManagedPtr does (except that there's no
shared_ptr involved, so the abstraction is clear). As I said,
from experience, it isn't that useful. The object with the
pointer generally needs to take some explicit action when the
pointed to object is destructed.
Of course, these are all things that any good developer takes
care of 100% of the time, right? Nobody ever leaves a
dangling pointer or neglects setting something to NULL...
Well, that's why you have code reviews
. And extensive
testing, with an instrumented system (Purify or valgrind, at the
least, a debugging operator new and operator delete, etc.).
It is true that really rigerous practices and good developers
that are very observant all the time might be able to get it
right all the time.
No one person gets it right all of the time. A well organized
team can get it right almost all of the time. And using
instrumentation like valgrind during testing can catch most of
the cases they miss, provided your test suites are thorough
enough. And using a garbage collector can ensure that the
memory which has been freed will not be reused as long as that
dangling pointer exists.
In my experience that's just too much to ask even of myself.
I **** up sometimes but if I'm using a device that renders my
fuckups nil or impossible it definitely makes my job easier on
both ends, coding and debugging.
Certainly, if such a device existed. But in my experience,
shared_ptr and its brethren, indiscriminately used (there are
cases where its appropriate), doesn't really reduce the
probability of fuckups; it just hides them in a way that makes
them more difficult to find.
Then you have the cases when your "navigation only" pointer
points at a stack allocated object.
That's actually very, very rare. Practically speaking, you can
only navigate between objects which have identity. (Copying an
object will screw up navigation tremendously.) And it's rare
(but not non-existant) for such objects not to be dynamically
allocated.
Believe it or not you can still use smart pointers here. Hold
a boost::shared_ptr to your local stack object (and you can
just stick these in a boost::any array if you want), return
weak_ptr objects from your accessor method. Granted, easily
broken so there's probably a better way (and I don't
necessarily recommend that method) with some smart pointer
object of your own but the idea is that everyone who has a
pointer now has some method of checking that the navigation is
still available before using it.
We're back to my ManagedPtr
.
On top of all that though there are many practices that create
dynamically generated objects that are totally reliant on
scope. My array example is one, pimpl (or handle/body) is
another and is one we use a lot.
Handle/body is definitely one, but 1) it's pretty easy to get
right without the smart pointer, and 2) the smart pointer you
want (boost::scoped_ptr) doesn't work in this case, since it can
only point to a completely defined type. You could use
shared_ptr for a pimpl, but I'd argue that that gives the wrong
message, since the pointed to object certainly isn't shared in
any way. Still, if there was a smart pointer with the basic
semantics of scoped_ptr, but which allowed instances in contexts
where the targetted object wasn't fully defined, like
shared_ptr, I'd buy it. (I suspect that scoped_ptr will
actually work here, *if* you don't forget to make the destructor
explicit, and implement it in the source file, where the impl
class is known. But it's not guaranteed, and I'd rather prefer
that the code refuse to compile if the destructor of the
scoped_ptr were instantiated in a context where the pointed to
type was not complete. All of which is probably doable.)
There are other methods one can take to respond to these
problems but I find expecting all pointers to be some form of
"smart" pointer object is the most straightforward, easiest to
check in code reviews, and objectively enforceable as a
standard. Any time you see a raw pointer you ask, "Why did
you do that?" Then you all have the option of designing a
smart pointer that addresses the particular problem that no
other did...or live with the raw pointer.
And I find the rule too constraining, given that most of my
pointers are for navigation, and most of the time, the owner of
the pointer needs to be registered as an observer anyway, for
other reasons, as well as in order to get rid of the pointer.
I also worry that the rule will cause the programmers to start
using shared_ptr everywhere, when it is rarely the appropriate
solution. With a large enough choice of smart pointers, it's
less of a problem, but for far too many programmers I've met,
smart pointer means boost::shared_ptr. And of course, there is
also a certain number of programmers who will look on the smart
pointer as a silver bullet, and stop thinking about the real
issues of object lifetime and notification; education and code
review should be able to solve those problems, but then, with
education and code review, the use of raw pointers ceases to be
a problem as well.