Re: C++ design question
From: Simon Elliott (Simon)
Date: 09/30/04
- Next message: Universe: "Re: XP Requirement Analysis?"
- Previous message: Shayne Wissler: "Re: XP Requirement Analysis?"
- Maybe in reply to: H. S. Lahman: "Re: C++ design question"
- Next in thread: Simon Elliott: "Re: C++ design question"
- Reply: Simon Elliott: "Re: C++ design question"
- Reply: H. S. Lahman: "Re: C++ design question"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: 30 Sep 2004 20:30:58 GMT
On 29/09/2004, H. S. Lahman wrote:
>
> The default constructor is always there, regardless of how many
> alternatives you supply, so anyone can instantiate it without
> defining i1_ and i2_ anyway.
>
> [There is a reason why it is a good practice to explicitly put the
> default constructor in the class header rather that allowing it to be
> implicit, but I forget what it is. I am a translationist, so I
> haven't written any C++ code in over a decade. B-)]
... or to make the default constructor private, as someone else here
has suggested. I'm already in the habit of declaring private copy
constructors and operator= unless I explicitly want the class to be
copyable. But this is just C++ minutia ...
[snip your diagram and description, not because it wasn't interesting
or useful, but because I don't want this post to get too long!]
> I think a simpler solution would be to create the Bar instance
> separately and initialize a pointer to it in Foo:
>
> class Foo
> {
> private:
> Bar* myBar;
> ...
> public:
> void DoStuffWithBarBase ()
> ...
> }
>
> fooDeriveN::DoStuffWithBarBase()
> {
> ...
> this.myBar.doIt();
> ...
> }
>
> Creator::createFooDeriveN(...)
> {
> Bar* myBar = new (barDeriveN(...));
> fooDerive* myFoo = new (fooDeriveN(myBar,...));
> ...
> }
Is the intention here for DoStuffWithBarBase() to be virtual, and for
each fooDerivedN to implement its own instance of this? This is a
perfectly valid way of approaching my cut down example, but I don't
want to do this in my real-world design because barBase knows how to do
a significant amount of complex tasks and must maintain a significant
state. I don't want this to be re-implemented (or even called down to)
from the barDerivedN as this would add significantly to the complexity.
And fooDeriveN::DoStuffWithBarBase() still only has a pointer to the
Bar base class to work with, so it can't access anything that
Foo::DoStuffWithBarBase() can't access.
However, in your example, we don't necessarily need
fooDeriveN::DoStuffWithBarBase() at all because we have a Bar* in Foo*.
Assuming that Bar is an abstract class which defines all the public
methods we'll need (ie the barDerivedN classes don't expose any more
functionality) then we can do everything we need from this Bar*.
> DoStuffWithBarBase now navigates to the Bar with confidence that it
> will do the right thing simply because it is at the end of the
> relationship. The rules for instantiating the relationship are
> encapsulated in createFooDeriveN or whoever creates Foos. Note that
> to invoke your Foo constructor, whoever does that has to know exactly
> the same things to provide the initializer list.
>
> This may seem like a trivial difference in encoding the constructor
> code, but it actually represents a more robust approach. That's
> because Foo doesn't need to know anything about the relationship
> rules or even what sort of Bar is on the other end of the
> relationship. One way that is manifested is that the Foo code only
> needs the Bar reference. [It also reduces the complexity of the
> constructor so that you safety problem goes away. B-)]
>
> A more important benefit is that if things change so that Bar is
> created separately for some reasons (e.g., because other objects need
> to access it or one decides one can optimize size by eliminating
> redundant embedded Bar instances or whatever) it is highly unlikely
> that the Foo constructor will have to change nor any other internals
> of Foo.
I don't think it would be possible to safely uncouple things quite that
much via this technique. If there's a Bar* in the Foo object, there's
always the risk that it might not be set, and that someone will then
try to access it. You could set Bar* myBar to zero in the constructor,
and then test for zero before you did anything that accessed the
pointer.
The advantage of the C++ reference is that it can't be unset: it always
must "point" to something.
> If you look at the way full UML code generators work, they invariably
> implement relationship navigation with pointers and collections of
> pointers because that allows very generic, aspect-like implementation
> that does not depend upon the class semantics at all. (They even use
> naming conventions that reflect relationship discriminators.) Among
> other things that allows the code generator to generate code for one
> class without even "looking" at the head files for other classes
> involved in the navigation.
I've not used UML very much as yet.
> That abstraction and separation of
> concerns is not possible in your reference approach because Foo needs
> to know the specific Bar subclass type even though it actually
> accesses through the Bar superclass.
I don't think it does. fooBase is implemented strictly in terms of
barBase. Or am I missing something here?
-- Simon Elliott http://www.ctsn.co.uk
- Next message: Universe: "Re: XP Requirement Analysis?"
- Previous message: Shayne Wissler: "Re: XP Requirement Analysis?"
- Maybe in reply to: H. S. Lahman: "Re: C++ design question"
- Next in thread: Simon Elliott: "Re: C++ design question"
- Reply: Simon Elliott: "Re: C++ design question"
- Reply: H. S. Lahman: "Re: C++ design question"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|