Paavo said:
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.)
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.
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.
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
against this.)
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.