Re: C++ Primer 4th edition Reference Counting Smart Pointers
Paavo Helde wrote:
Pointers are needed for entity objects, and those do not come in arrays.
Value objects can be held in std::vector, for example. In 10 years of C++
I have not yet encountered a need to have a smartpointer to an array.
The problem arises when you need to *share* the same array among
multiple entities (which might even reside in different threads). In
some situations it might not be completely clear which entity is the
last one to reference the array data. (Which one is the last to
reference the array might even be non-deterministic, in the case of
sharing between multiple threads.)
A pointer can point to an incomplete type. Making a smart pointer
correctly support incomplete types is possible, but not glaringly
obvious. (Except in the case of intrusive smart pointers, for which
it's just not possible.)
The worst case would be silent misbehavior upon deletion because of using
incomplete type. One solution to this is to use intrusive smart pointers.
Using intrusive smart pointers is only a "solution" in that it
completely disallows using incomplete types. (If that's the preferred
"solution", then I'm sure it would be possible to make a non-intrusive
smart pointer also give a compile-time error on incomplete types.)
It doesn't solve the problem if you would need pointers to incomplete
types for whatever reason.
An object might have been allocated with something else than the
default system allocator. Many naive smart pointer implementations out
there don't support deallocation using that same special allocator.
(Also, most don't support allocating their own ancillary data using a
user-specified allocator, rather than the default system allocator. I
think even the boost smart pointers don't support this.)
One solution to this is to use intrusive smartpointers.
I don't see how. Whether the smart pointer is intrusive or not has no
effect on how it should destroy the object. If the object was allocated
using some custom allocator, the smart pointer should use the same
allocator to destroy the object, regardless of whether the smart pointer
is intrusive or not.
Making a smart pointer safe to use is extremely difficult, if not
impossible. It's basically impossible to make sure that the same
pointer is not given to two different (non-instrusive) smart pointers.
Or that a pointer to something not allocated with 'new' is not given
You are right, one solution to this is to use intrusive smartpointers.
Intrusive smart pointers have no protection against someone giving
them a pointer to an object not allocated dynamically.
(Basically avoiding the problem is left to the user of the smart
pointer. The smart pointer itself has no way of protecting itself
Intrusive smart pointers have their own gotchas, most of which naive
implementations get wrong. (For example most of them fail to take into
account that objects might be directly assigned to other objects,
which can mess up the reference counter if not specifically taken into
In my experience, smartpointers are needed only for entity objects, which
usually do not support direct assignment anyway, so the problem does not
arise often. The intrusive reference counter should reside in a separate
base class anyway, implementing the proper copy/assignment behavior, so
one has to take this problem into account only once.
Making intrusive objects safe for assignment is easy, but most people
who make naive intrusive smart pointer implementations never realize the
problem exists, and thus never implement the solution. That's why when
you see a first-timer implementing such a smart pointer, you can be
almost 100% sure it will have this precise problem.
Of course at a more general level, smart pointers have other problems
as well, which cannot be easily guarded against, at least not by the
pointer itself. Recursive referencing is the most famous one, but not
the only one.
A much subtler problem can happen also in another kind-of recursive
situation. For example, if module A owns an object B (and manages it
using a smart pointer), and a function of B calls a function of A which
might drop the object in question, the destruction can happen in the
middle of the B function execution. In other words, when the function in
A returns, and the function in B which called it continues, it might be
operating on a destroyed object. Mayhem ensues.
The only possible safeguard against this is that every function in B
which calls an outside module increments the reference counter at the
beginning of the function and decrements it at the end (assuming it has
a way of doing this). How much overhead this adds to the function
depends on the situation.