Re: C++ design question
From: Simon Elliott (Simon)
Date: 09/29/04
- Next message: Phlip: "Re: XP Requirement Analysis?"
- Previous message: Mark Nicholls: "Re: SubTyping versus SubClassing"
- In reply to: H. S. Lahman: "Re: C++ design question"
- Next in thread: H. S. Lahman: "Re: C++ design question"
- Reply: H. S. Lahman: "Re: C++ design question"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: 29 Sep 2004 10:17:28 GMT
On 28/09/2004, H. S. Lahman wrote:
> > I've been advised by the guys over on comp.lang.c++ to post this
> > here to see if anyone can recommend a design improvement. In the
> > following code example, the base class depends on an object in a
> > derived class: fooBase has a reference to an object in fooDerived.
> > It's been pointed out that this is potentially unsafe if
> > bar_base_ref_ is accessed in the fooBase constructor.
>
> I see this as a C++ problem rather than a design problem...
See below.
> > class fooDerived:public fooBase
> > {
> > private:
> > barDerived my_bar_derived_;
> > public:
> > fooDerived(int i1, int i2):fooBase(my_bar_derived_),
> > my_bar_derived_(42,43){}
> > virtual ~fooDerived(void){};
> > };
>
> It seems to me the basic problem exists in invoking class
> constructors of other objects as if they were attribute assignments.
> C++ may allow such nonsense, but that doesn't mean you have an
> obligation to shoot yourself in the foot. Move both initializations
> inside the ellipses so that you have control over the sequencing:
>
> fooDerived (int i1, int i2)
> {
> this.bar_base_ref_ = (barBase&) my_bar_dervice;
> this.my_bar_derived.setI1(i1);
> this.my_bar_derived.setI2(i2);
> }
AFAIK this isn't valid C++. It's necessary to initialise references
from the constructor's init list.
>
> Why not invoke the barDerived constructor? Because constructors are
> notoriously fragile and it is wise to do as little as possible inside
> them. A good rule of thumb is to limit them to simple data
> assignments. Explicitly invoking another constructor within a
> constructor is an unnecessary complication. If the initialization of
> an instance is necessarily complex, there should be a separate
> initializer method that separates those rules and policies from the
> more mundane housekeeping of instantiation.
That would mean removing barDerived's current constructor, and
replacing it (explicitly or implicitly) with a default constructor.
Which would allow users of this class to instantiate it without
initialising i1_ and i2_.
> However, I have a more abstract question: Why have both
> my_bar_derived and bar_base_ref_? my_bar_derived is private so
> there is no issue of it being accessed by some external object.
> Since it is an embedded object, it is a matter of private
> implementation to know that it is a member of the barDerived class.
> Therefore casting of bar_base_ref_ does not risk type safety if
> specialized properties are accessed within fooDerived.
This is an interesting question, which addresses design issues rather
than C++ semantics.
The reason fooBase has a bar_base_ref_ is to allow this to work:
my_foo_derived.DoStuffWithBarBase();
In the full scale design, DoStuffWithBarBase() calls a virtual function
in barBase which does different things depending on the implementation
of barDerived. And there's a fooDerived1 with corresponding
barDerived1, fooDerived2 with corresponding barDerived2, fooDerivedN
with corresponding barDerivedN.
However, this design relies on an object in a derived class, which puts
a burden on developers of the various fooDerivedN.
I'm also looking at an alternative design in which fooBase has a pure
virtual method which returns a reference to barBase. This means that
developers of the various fooDerivedN can't forget to include the
method, otherwise their derived class can't be instantiated. And it
doesn't require that the reference be initialised in the constructor.
It looks something like this (note unchecked code here):
class fooBase
{
private:
fooBase(const fooBase &C);
fooBase& operator=(const fooBase &C);
protected:
virtual barBase& GetBarBase(void) = 0;
public:
fooBase(){}
virtual ~fooBase(void){};
void DoStuffWithBarBase(void)
{
GetBarBase().SetI1(13);
}
};
class fooDerived:public fooBase
{
private:
barDerived my_bar_derived_;
protected:
virtual barBase& GetBarBase(void)(return(my_bar_derived_);}
public:
fooDerived(int i1, int i2):fooBase(),
my_bar_derived_(i1,i2){}
virtual ~fooDerived(void){};
};
However, in real life I think I'll need a const and non const version
of GetBarBase, and in real life I have several aggregated objects, so
I'd need to ask developers of derived classes to supply several virtual
Get???? functions. They are pure virual in the base class so they can't
forget, but still seems like a pain.
A third option would be to have a pointer *barBase in fooBase, and
allocate a new barDerived in fooDerived. At least then fooBase would
own the object. But I get the feeling that it would be tricky to make
this exception safe, and it seems asymetric for a derived class to be
responsible for allocating something, and the base class for deleting
it.
-- Simon Elliott http://www.ctsn.co.uk
- Next message: Phlip: "Re: XP Requirement Analysis?"
- Previous message: Mark Nicholls: "Re: SubTyping versus SubClassing"
- In reply to: H. S. Lahman: "Re: C++ design question"
- Next in thread: H. S. Lahman: "Re: C++ design question"
- Reply: H. S. Lahman: "Re: C++ design question"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|