Daniel said:
My convention isn't the only one that requires discipline. Use of a
smart pointer also requires discipline. When an object is placed in a
smart pointer, one must ensure that (a) the object was dynamically
allocated, (b) that the smart pointer knows how to properly deallocate
it and (c) no other pointer to that object exists either as a raw
pointer, or within another smart pointer type.
Of course you are right that different discipline is also required for
proper smart pointer usage. My point (perhaps poorly communicated) was
primarily that *if* the smart pointer is used properly (just as it is
expected that one will not pass a dangling reference/pointer into
A::A() in your convention), then passing one to or returning one from a
function clearly communicates and enforces expectations for the
life-time management of that object. Use of smart pointers are
certainly not fool-proof, but using them across the board generally
makes things simpler and safer (especially when exceptions come into
play).
Compared to this explicitness, using raw pointers/references
necessitates nothing about life-time management since one can use them
"properly" in a number of ways -- your excellent convention being just
one of those. Smart pointers have (relatively) clearly defined rules
and expectations; raw pointers do not. Your convention may exist in
your personal or your company's coding standards, the written
documentation, or code comments, but that makes it more dependent on
the people involved and their discipline. Making life-time management
instead depend on the language rules via RAII and smart pointers makes
code more robust.
So given, for example, two libraries that I want to use which use two
different smart pointers, what benefit do they give me when I want to
share an object between them? If anything my plight is worse than if
they (or at least one of them) used raw pointers.
Well, if one uses std::auto_ptr and the other uses
std::tr1::shared_ptr, obviously they are incompatible because of
differing requirements, and such a case would be incompatible in your
convention as well, though I would hasten to point out that, unlike
with smart pointers, this incompatibility would not be clear just from
the interface.
If one uses std::tr1::shared_ptr and the other a roughly equivalent
reference-counted smart pointer (e.g., Loki::SmartPtr), then you do
have a problem, though you might be able to resolve it by supplying
compatible policies (for Loki::SmartPtr) or with a partial
specialization of the smart pointer class (for some other
implementation). ISTM that that problem will be mitigated somewhat
if/when shared_ptr becomes an official part of the standard. Then you
could justly complain to the library vendor that they should use
standard features rather than non-standard but equivalent ones.
In my experience, most third-party libraries today don't offer smart
pointers in their interfaces mainly because of the non-standard nature
of the beasts. Hopefully, that will change for the better if/when the
standardization includes more smart pointers, but for the time being,
one can use smart pointers internally in the project and convert them
to raw pointers when sending to third-party libraries (cf. using
std::vector with C APIs).
Please understand I think smart pointers are a great idea, they are used
to excellent effect in some languages where every object is held by one.
But unless and until one smart pointer class holds every dynamically
allocated object no matter what library creates it, and cannot be
accidentally made to hold objects that were not dynamically allocated,
use of the smart pointer requires no less discipline, nor is it any less
error prone than use of a raw pointer.
I agree fully that the discipline required for using smart pointers is
different, but I would argue that their use is indeed less error prone,
especially in the face of exceptions. The same thing is true of using
std::vector instead of C-style arrays: though the former requires
different discipline for proper usage (e.g., making sure iterators are
not invalidated), its benefits -- clearly defined life-time management,
exception-safety, and elimination of common programmer errors (e.g.,
delete instead of delete[]) -- out-weigh the downsides in most
circumstances (cf. FAQ 34.1).
Cheers! --M