Re: C++ design question

From: Simon Elliott (Simon)
Date: 09/30/04


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


Relevant Pages

  • Re: getting used to Java - question about "style"
    ... > constructor." ... Let's suppose you create a class Foo ... ... They can also override frob(), because we didn't make it private or final. ... When a new object of type Bar is created, ...
    (comp.lang.java.programmer)
  • Re: Why cant I call a parameterless-constructor from a baseclass ?
    ... public class foo ... public class bar: foo ... constructor for bar that takes an int, ... not specify any constructor at all for a class, ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: C++ design question
    ... The default constructor is implied in the initialization list. ... One wants to access Bar at the superclass level to avoid static typing ... Usually that would be done by whoever instantiates a Foo subclass ...
    (comp.object)
  • Re: Creating an object that is read from an input stream.
    ... You can use the copy constructor to implement operator>> like so: ... Foo foo; ... Bar bar; ... // use new class MyClass that has a MyClass_base pointer ...
    (comp.lang.cpp)
  • Re: Insert with response
    ... FooBar, there's no way and no need to put them in synch. ... column in the foo table to 250 calumns in the bar table. ... set statistics time off ...
    (microsoft.public.sqlserver.programming)