Re: C++ design question
From: kurth (kurth_at_avanade.com)
Date: 09/29/04
- Next message: Universe: "Re: XP Requirement Analysis?"
- Previous message: Rhycardo: "Principle of Holyhood"
- 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: Tue, 28 Sep 2004 23:11:37 -0600
"H. S. Lahman" <h.lahman@verizon.net> wrote in message
news:q2h6d.3092$OX.3079@trndny07...
> Responding to Elliott...
>
>> 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...
>
>> 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:
There are several cases where an initialization list is required.
- call non-default base class constructor
- initialize constant members
- initialize reference members (&)
The initializers are called in the order they are declared rather then the
order listed in the initialization list.
class A {};
class B
{
private:
B() {} //. no default constructor
public:
B(int x) {}
};
class S
{
char buffer[1024];
public:
S() { memset(buffer, 0, sizeof(buffer)); }
S(char c) { memset(buffer, c, sizeof(buffer)) }
};
class C : public A, pubic B
{
S X;
const char Y;
S &Z;
C(S &x, char y) : B(5), Y(y), Z(Y), A() {} {
X = x;
}
}
The memory will first be allocated for sizeof(C) (= sizeof(A) + sizeof(B) +
sizeof(S) + sizeof(char) + sizeof(S&)). This memory is uninitialized it
contains whatever happened to be in memory at the time, aka garbage (note:
in debug builds usually memory is zero'd) . The members are then
initialized in the order declared first the base classes and then finally
members of derived class, so the actual initialization looks like A(), B(5),
X(), Y(y), Z(Y), {X.operator=(x)}.
This is why it was unsafe for fooDerived to pass a member to fooBase the
fooDerived's members have not yet initialized (even after construction has
completed it still will not work because fooBase has copied the
uninitialized address).
Notice that Y is const and must be in the initialization list.
Z is a reference and must be in the initialization list.
Additionally Z takes Y as a parameter this will work because Y is
initialized before Z. If we defined C as { S X; S &Z; const char Y; ... }
value of Y would be still be garbage producing undefined results.
Also note that X was implicitly initialized prior to { X = x; } resulting
setting 1024 bytes to 0 unnecessarily, if there was no default constructor
you would have recieved an compile time error. By adding X(x) to the
initialization list the copy constructor would have been invoked rather then
the default constructor initializing it imediatly to the desired char,
rather then the calling the default assignment operator within the {}.
There isn't any difference with buit-in types int and the like because they
are not implicitly initialized as X was.
Initialization lists are actually the recommended method to initialize
members during construction. In some cases they are required and in other
situations they may provide a slight performance beneift. However they can
be difficult to read, and the performance benefit is probably not worth the
added complexity if not well understood by those maintaining it. In
summary, if you need them you have no choice, if they provide a significant
performance benifit use them reluctantly ( clock cycles are cheap these
days), if you can avoid them avoid them.
- Kurt
>
> 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);
> }
>
> 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.
>
> 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.
>
> *************
> There is nothing wrong with me that could
> not be cured by a capful of Drano.
>
> H. S. Lahman
> hsl@pathfindermda.com
> Pathfinder Solutions -- Put MDA to Work
> http://www.pathfindermda.com
> blog (under constr): http://pathfinderpeople.blogs.com/hslahman
> (888)-OOA-PATH
>
>
>
>
- Next message: Universe: "Re: XP Requirement Analysis?"
- Previous message: Rhycardo: "Principle of Holyhood"
- 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
|