Re: Never ever use a raw pointer when a smart pointer can do the
same job
* Noah Roberts:
Alf P. Steinbach wrote:
* Noah Roberts:
Alf P. Steinbach wrote:
* Noah Roberts:
Alf P. Steinbach wrote:
* Noah Roberts:
and there's the other case when you don't care but sometime down
the road someone decides that a subclass should be returned
rather than the one you're constructing.
You don't. That's totally evil. You're *changing the behavior*
without changing the type, leaving client code stranded with
mysterious behavior change.
That's only true if one of two things is true:
* Your client code is depending on the private behavior of the
class instead of the advertised pre/post conditions.
* Your new sub object violates the advertized pre/post conditions
of the class it inherits from.
If the new class changes nothing wrt. client code, then there is
absolutely no reason to foist a replacement on the client code.
I thought you'd understand that from my comments, but I wasn't clear
enough.
So let me put it this way: *either* the new class is different in
some way that affects behavior, in which case you're changing the
behavior, or else it's not, in which case it's not very smart to do
a lot of work to replace the original.
Sounds to me like maybe your classes depend too much on HOW things do
something rather than that they do. A stack drawing client doesn't
care how the items in the stack draw, it only cares that they do.
The drawn objects don't care how the stack drawing client decides to
draw them, and in fact probably don't even know they're in a stack at
all. A hit check algorithm doesn't care how the objects it's
checking give it a boundary polygon, only that they do and that this
polygon represents the boundaries of the object in question; the
objects returning the polygon do not care or know how the hit check
algorithm works, only that it needs a polygon.
All clients should be thus.
This is the essence of OOP: you can change the behavior of your
application by extending the objects that implement it without having
to make sweeping changes across the whole codebase. It's also the
core reason behind the most important principle of OOP: the
open/closed. This is done by making sure objects are not
irresponsibly coupled to each other, INCLUDING (most importantly
probably) that client code is not coupled to the implementation
details behind an interface.
This is the core of encapsulation.
Once again, I'm quite surprised at your argument. This is back to
the basics stuff.
It doesn't seem that you understood it.
But let's take your first example, the stack.
You're saying a factory function is nice because it allows you to
change the stack representation without updating the clients.
If (A) the new stack class doesn't change anything wrt. the client
code, then it's just so much waste.
If it does change something, e.g. allowing *new* client code to use
some extra features or whatever, then either (B) it conforms to the
old interface, in which case the original class should just be
replaced with the new one (and no need for a factory function to do
that),
or else (C)
it doesn't conform to the old interface, in which case it absolutely
shouldn't be foisted on old client code.
I'd be surprised if you can think of a case I haven't enumerated here.
(D) case (B) but you want to replace the object based upon state of the
application that nobody else involved cares about or knows. We might
hypothesize a case (using the example given) when based on some user
selection criteria the stack draws in one order verses another. With
this simple example this may not be the best way of implementing that
choice (you might chose instead to implement a State pattern in the draw
stack - still vastly changing internals though), but it would certainly
be one way and with more complex systems it could very well be the best
or only one.
This is not a case of "sometime down the road" someone replacing the compile
time class.
It's a case of knowing up front that different classes may be involved at run time.
So it's not an additional case for what we discussed, where it's still A, B or C.
This is a perfect use of factory method.
It may be, depending on the concrete details.
But it has nothing to do with your claim.
Also, it has nothing to do with ensuring dynamic allocation.
It is, in fact, the very reason to use one.
Uh.
We might further hypothesize that this is a new requirement in an old
product. Had you chosen to use "new DrawStack()" rather than
"DrawStack::Create()" you have a lot more editing to do to provide this
change.
I don't buy that argument. For it can be used to add any additional dead code
that supports some limited functionality. Hey, if something needing this
functionality should become a requirement then we're already some steps in that
direction (oh dang, now we're there and it's not quite like imagined, or, oh
dang, now we're way past the point where that could happen). It's not even
necessarily a good idea when you /know/ that something like that will become a
requirement. For you're then in the position of those who paid extra for
"prepared for stereo" radios that, the manufacturer claimed, could easily be
upgraded to stereo: at the time stereo broadcasts were finally made those who
bought them had to buy new radios anyway, the old "prepared" radios scrapped.
(E) you've decided that the interface in question needs to be changed to
an abstract interface entirely, the creation parameterized, and a
default provided for "legacy" clients.
This is an example of the case I labeled (B), already discussed.
Again, case (B) mostly with the
additional issue that other clients are providing further information
they know about and you don't want to couple anyone to the hierarchy you
are encapsulating through the abstract interface.
When you need to force someone to use heap allocation (again the factory
method won't work with stack allocation) then usually you're well on
your way toward these cases anyway for you're probably doing something
with a polymorphic interface.
No.
First, polymorphism doesn't generally force heap allocation (mostly heap
allocation is forced, when it is, by the need for self destruction, or as a way
to avoid slicing, or e.g. by the size of the object, or other concerns not
directly related to polymorphism).
Second, any polymorphic treatment is usually at sites later referencing the
created object: at the creation site one usually knows exactly what kind of
object one needs created.
It is well warranted to decouple clients
from the static return type of your creation method then.
The premise for that doesn't hold: it's not usual, but rather unusual, that the
code creating an object needs to be held unaware of the exact type created.
In languages like Java it can be a good general step that costs nothing
extra to implement for any class you author...in C++ it's a bit
different and reserved for special cases.
Huh?
Cheers & hth.,
- Alf