荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: jjksam ([==面壁大师==]), 信区: Program
标  题: C++ FAQ (part 10 of 14)
发信站: 荔园晨风BBS站 (Mon Jul  1 14:36:14 2002), 转信



From: cline@parashift.com
Sender: cline@parashift.com
Newsgroups: comp.lang.c++,comp.answers,news.answers,alt.comp.lang.learn.c-c++
Subject: C++ FAQ (part 10 of 14)
Summary: Please read this before posting to comp.lang.c++
Followup-To: comp.lang.c++
Reply-To: cline@parashift.com (Marshall Cline)
Distribution: world
Approved: news-answers-request@mit.edu
Expires: +1 month

Archive-name: C++-faq/part10
Posting-Frequency: monthly
Last-modified: Jun 17, 2002
URL: http://www.parashift.com/c++-faq-lite/

AUTHOR: Marshall Cline / cline@parashift.com / 972-931-9470

COPYRIGHT: This posting is part of "C++ FAQ Lite."  The entire "C++ FAQ Lite"
document is Copyright(C)1991-2002 Marshall Cline, Ph.D., cline@parashift.com.
All rights reserved.  Copying is permitted only under designated situations.
For details, see section [1].

NO WARRANTY: THIS WORK IS PROVIDED ON AN "AS IS" BASIS.  THE AUTHOR PROVIDES
NO
WARRANTY WHATSOEVER, EITHER EXPRESS OR IMPLIED, REGARDING THE WORK, INCLUDING
WARRANTIES WITH RESPECT TO ITS MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
PURPOSE.

C++-FAQ-Lite != C++-FAQ-Book: This document, C++ FAQ Lite, is not the same as
the C++ FAQ Book.  The book (C++ FAQs, Cline and Lomow, Addison-Wesley) is
500%
larger than this document, and is available in bookstores.  For details, see
section [3].

==============================================================================

SECTION [21]: Inheritance -- proper inheritance and substitutability


[21.1] Should I hide member functions that were public in my base class?

Never, never, never do this.  Never.  Never!

Attempting to hide (eliminate, revoke, privatize) inherited public member
functions is an all-too-common design error.  It usually stems from muddy
thinking.

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

==============================================================================

[21.2] Derived* --> Base* works OK; why doesn't Derived** --> Base** work?

C++ allows a Derived* to be converted to a Base*, since a Derived object is a
kind of a Base object.  However trying to convert a Derived** to a Base** is
flagged as an error.  Although this error may not be obvious, it is
nonetheless
a good thing.  For example, if you could convert a Car** to a Vehicle**, and
if
you could similarly convert a NuclearSubmarine** to a Vehicle**, you could
assign those two pointers and end up making a Car* point at a NuclearSubmarine:


 class Vehicle {
 public:
   virtual ~Vehicle() { }
   virtual void startEngine() = 0;
 };

 class Car : public Vehicle {
 public:
   virtual void startEngine();
   virtual void openGasCap();
 };

 class NuclearSubmarine : public Vehicle {
 public:
   virtual void startEngine();
   virtual void fireNuclearMissle();
 };

 int main()
 {
   Car   car;
   Car*  carPtr = &car;
   Car** carPtrPtr = &carPtr;
   Vehicle** vehiclePtrPtr = carPtrPtr;  // This is an error in C++
   NuclearSubmarine  sub;
   NuclearSubmarine* subPtr = ⊂
   *vehiclePtrPtr = subPtr;
   // This last line would have caused carPtr to point to sub !
   carPtr->openGasCap();  // This might call fireNuclearMissle()!
 }

In other words, if it was legal to convert a Derived** to a Base**, the Base**
could be dereferenced (yielding a Base*), and the Base* could be made to point
to an object of a different derived class, which could cause serious problems
for national security (who knows what would happen if you invoked the
openGasCap() member function on what you thought was a Car, but in reality it
was a NuclearSubmarine!! Try the above code out and see what it does -- on
most
compilers it will call NuclearSubmarine::fireNuclearMissle()!

(BTW you'll need to use a pointer cast to get it to compile.  Suggestion: try
to compile it without a pointer cast to see what the compiler does.  If you're
really quiet when the error message appears on the screen, you should be able
to hear the muffled voice of your compiler pleading with you, "Please don't
use
a pointer cast! Pointer casts prevent me from telling you about errors in your
code, but they don't make your errors go away! Pointer casts are evil[26.10]!"
At least that's what my compiler says.)

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

==============================================================================

[21.3] Is a parking-lot-of-Car a kind-of parking-lot-of-Vehicle?

Nope.

I know it sounds strange, but it's true.  You can think of this as a direct
consequence of the previous FAQ, or you can reason it this way: if the kind-of
relationship were valid, then someone could point a parking-lot-of-Vehicle
pointer at a parking-lot-of-Car.  But parking-lot-of-Vehicle has a
addNewVehicleToParkingLot(Vehicle&) member function which can add any Vehicle
object to the parking lot.  This would allow you to park a NuclearSubmarine in
a parking-lot-of-Car.  Certainly it would be surprising if someone removed
what
they thought was a Car from the parking-lot-of-Car, only to find that it is
actually a NuclearSubmarine.

Another way to say this truth: a container of Thing is not a kind-of container
of Anything even if a Thing is a kind-of an Anything.  Swallow hard; it's true.


You don't have to like it.  But you do have to accept it.

One last example which we use in our OO/C++ training courses: "A Bag-of-Apple
is not a kind-of Bag-of-Fruit." If a Bag-of-Apple could be passed as a
Bag-of-Fruit, someone could put a Banana into the Bag, even though it is
supposed to only contain Apples!

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

==============================================================================

[21.4] Is an array of Derived a kind-of array of Base?

Nope.

This is a corollary of the previous FAQ.  Unfortunately this one can get you
into a lot of hot water.  Consider this:

 class Base {
 public:
   virtual void f();             // 1
 };

 class Derived : public Base {
 public:
   // ...
 private:
   int i_;                       // 2
 };

 void userCode(Base* arrayOfBase)
 {
   arrayOfBase[1].f();           // 3
 }

 int main()
 {
   Derived arrayOfDerived[10];   // 4
   userCode(arrayOfDerived);     // 5
 }

The compiler thinks this is perfectly type-safe.  Line 5 converts a Derived*
to
a Base*.  But in reality it is horrendously evil: since Derived is larger than
Base, the pointer arithmetic done on line 3 is incorrect: the compiler uses
sizeof(Base) when computing the address for arrayOfBase[1], yet the array is
an
array of Derived, which means the address computed on line 3 (and the
subsequent invocation of member function f()) isn't even at the beginning of
any object! It's smack in the middle of a Derived object.  Assuming your
compiler uses the usual approach to virtual[20] functions, this will
reinterpret the int i_ of the first Derived as if it pointed to a virtual
table, it will follow that "pointer" (which at this point means we're digging
stuff out of a random memory location), and grab one of the first few words of
memory at that location and interpret them as if they were the address of a
C++
member function, then load that (random memory location) into the instruction
pointer and begin grabbing machine instructions from that memory location.
The
chances of this crashing are very high.

The root problem is that C++ can't distinguish between a pointer-to-a-thing
and
a pointer-to-an-array-of-things.  Naturally C++ "inherited" this feature from
C.

NOTE: If we had used an array-like class (e.g., std::vector<Derived> from the
standard library[34.1]) instead of using a raw array, this problem would have
been properly trapped as an error at compile time rather than a run-time
disaster.

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

==============================================================================

[21.5] Does array-of-Derived is-not-a-kind-of array-of-Base mean arrays are
       bad?

Yes, arrays are evil[33.1].  (only half kidding).

Seriously, arrays are very closely related to pointers, and pointers are
notoriously difficult to deal with.  But if you have a complete grasp of why
the above few FAQs were a problem from a design perspective (e.g., if you
really know why a container of Thing is not a kind-of container of Anything),
and if you think everyone else who will be maintaining your code also has a
full grasp on these OO design truths, then you should feel free to use arrays.
But if you're like most people, you should use a template container class such
as std::vector<T> from the standard library[34.1] rather than raw arrays.

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

==============================================================================

[21.6] Is a Circle a kind-of an Ellipse?

Depends.  But not if Ellipse guarantees it can change its size asymmetrically.

For example, if Ellipse has a setSize(x,y) member function that promises the
object's width() will be x and its height() will be y, Circle can't be a
kind-of Ellipse.  Simply put, if Ellipse can do something Circle can't, then
Circle can't be a kind of Ellipse.

This leaves two potential (valid) relationships between Circle and Ellipse:
 * Make Circle and Ellipse completely unrelated classes
 * Derive Circle and Ellipse from a base class representing "Ellipses that
   can't necessarily perform an unequal-setSize() operation"

In the first case, Ellipse could be derived from class AsymmetricShape, and
setSize(x,y) could be introduced in AsymmetricShape.  However Circle could be
derived from SymmetricShape which has a setSize(size) member function.

In the second case, class Oval could only have setSize(size) which sets both
the width() and the height() to size.  Ellipse and Circle could both inherit
from Oval.  Ellipse --but not Circle-- could add the setSize(x,y) operation
(but beware of the hiding rule[23.5] if the same member function name
setSize()
is used for both operations).

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

(Note: setSize(x,y) isn't sacred.  Depending on your goals, it may be okay to
prevent users from changing the dimensions of an Ellipse, in which case it
would be a valid design choice to not have a setSize(x,y) method in Ellipse.
However this series of FAQs discusses what to do when you want to create a
derived class of a pre-existing base class that has an "unacceptable" method
in
it.  Of course the ideal situation is to discover this problem when the base
class doesn't yet exist.  But life isn't always ideal...)

==============================================================================

[21.7] Are there other options to the "Circle is/isnot kind-of Ellipse"
       dilemma?

If you claim that all Ellipses can be squashed asymmetrically, and you claim
that Circle is a kind-of Ellipse, and you claim that Circle can't be squashed
asymmetrically, clearly you've got to adjust (revoke, actually) one of your
claims.  You can get rid of Ellipse::setSize(x,y), get rid of the inheritance
relationship between Circle and Ellipse, or admit that your Circles aren't
necessarily circular.  (You can also get rid of Circle completely, where
circleness is just a temporary state of an Ellipse object rather than a
permanent quality of the object.)

Here are the two most common traps new OO/C++ programmers regularly fall into.
They attempt to use coding hacks to cover up a broken design (they redefine
Circle::setSize(x,y) to throw an exception, call abort(), choose the average
of
the two parameters, or to be a no-op).  Unfortunately all these hacks will
surprise users, since users are expecting width() == x and height() == y.  The
one thing you must not do is surprise your users.

If it is important to you to retain the "Circle is a kind-of Ellipse"
inheritance relationship, you can weaken the promise made by Ellipse's
setSize(x,y).  E.g., you could change the promise to, "This member function
might set width() to x and/or it might set height() to y, or it might do
nothing".  Unfortunately this dilutes the contract into dribble, since the
user
can't rely on any meaningful behavior.  The whole hierarchy therefore begins
to
be worthless (it's hard to convince someone to use an object if you have to
shrug your shoulders when asked what the object does for them).

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

(Note: setSize(x,y) isn't sacred.  Depending on your goals, it may be okay to
prevent users from changing the dimensions of an Ellipse, in which case it
would be a valid design choice to not have a setSize(x,y) method in Ellipse.
However this series of FAQs discusses what to do when you want to create a
derived class of a pre-existing base class that has an "unacceptable" method
in
it.  Of course the ideal situation is to discover this problem when the base
class doesn't yet exist.  But life isn't always ideal...)

==============================================================================

[21.8] But I have a Ph.D. in Mathematics, and I'm sure a Circle is a kind of
an
       Ellipse! Does this mean Marshall Cline is stupid? Or that C++ is
stupid?
       Or that OO is stupid?

Actually, it doesn't mean any of these things.  But I'll tell you what it does
mean (you may not like what I'm about to say): it means your intuitive notion
of "kind of" leading you to make bad inheritance decisions.  Your tummy is
lying to you about what good inheritance really means -- stop believing those
lies.

Look, I have received and answered dozens of passionate e-mail messages about
this subject.  I have taught it hundreds of times to thousands of software
professionals all over the place.  I know it goes against your intuition.  But
trust me; your intuition is wrong (where "wrong" means "will cause you to make
bad decisions in OO design and programming").

Here's how to make good decisions in OO design and programming (at least those
decisions that have to do with inheritance): recognize that the derived class
objects must be substitutable for the base class objects.  That means objects
of the derived class must behave in a manner consistent with the promises made
in the base class' contract.  Once you believe this (and I fully recognize
that
you might not yet, but you will if you work at it with an open mind), you'll
see that setSize(x,y) violates this substitutability.

There are three ways to fix this problem:

 1. Soften the promises made by setSize(x,y) in base class Ellipse (or perhaps
    remove it completely), at the risk of breaking existing code that calls
    setSize(x,y).

 2. Strengthen the promises made by setSize(x,y) in the derived class Circle
    (which really means allowing a Circle to have a different height than
width
    -- an asymmetrical circle; hmmm).

 3. Drop the inheritance relationship (possibly getting rid of class Circle
    completely; i.e., considering circleness to be a temporary state of an
    Ellipse rather than a permanent constraint on the object).

Sorry, but there simply are no other choices.

Another way to say this is that you have to either make the base class weaker
(in this case weaken Ellipse to the point that it no longer guarantees you can
set its width and height to different values), make the derived class stronger
(in this case empower a Circle with the ability to be both symmetric and, ahem,

asymmetric), or admit that a Circle is not substitutable for Ellipse.

Important: there really are no other choices than the above three.  In
particular:

 1. PLEASE don't write me and tell me that a fourth option is to derive both
    Circle and Ellipse from a third common base class.  That's not a fourth
    solution.  That's just a repackaging of solution #3: it works precisely
    because it removes the inheritance relationship between Circle and Ellipse.


 2. PLEASE don't write me and tell me that a fourth option is to prevent users
    from changing the dimensions of an "Ellipse." That is not a fourth
    solution.  That's just a repackaging of solution #1: it works precisely
    because it removes that guarantee that setSize(x,y) actually sets the
width
    and height.

 3. PLEASE don't write me and tell me that you've decided one of these three
is
    "the best" solution.  Doing that would show you had missed the whole point
    of this FAQ (that bad inheritance is subtle but fortunately you have three
    [not one; not two; but three] possible ways to dig yourself out).  So when
    you run into bad inheritance, please try all three of these techniques and
    select the best (or perhaps "least bad") of the three.  Don't throw out
two
    of these tools ahead of time: try them all.

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

==============================================================================

[21.9] Perhaps Ellipse should inherit from Circle then?

If Circle is the base class and Ellipse is the derived class, then you run
into
a whole new set of problems.  For example, suppose Circle has a radius()
method.  Then Ellipse will also need to have a radius() method, but that
doesn't make much sense: what does it even mean for a (possibly assymetric)
ellipse to have a radius?

If you get over that hurdle (e.g., by having Ellipse::radius() return the
average of the major and minor axes, or whatever), then there is a problem
with
the relationship between radius() and area().  E.g., suppose Circle has an
area() method that promises to return 3.14159[etc] times the square whatever
radius() returns.  Then either Ellipse::area() will not return the true area
of
the ellipse, or you'll have to stand on your head to get radius() to return
something that matches the above formula.

Even if you get past that one (i.e., by having Ellipse::radius() return the
square root of the ellipse's area divided by pi), you'll get stuck by the
circumference() method.  E.g., suppose Circle has a circumference() method
that
promises to return two times pi times whatever is returned by radius().  Now
you're stuck: there's no way to make all those constraints work out for
Ellipse: the Ellipse class will have to lie about its area, its circumference,
or both.

Bottom line: you can make anything inherit from anything provided the methods
in the derived class abide by the promises made in the base class.  But you
ought not to use inheritance just because you feel like it, or just because
you
want to get code reuse.  You should use inheritance (a) only if the derived
class's methods can abide by all the promises made in the base class, and (b)
only if you don't think you'll confuse your users, and (c) only if there's
something to be gained by using the inheritance -- some real, measurable
improvement in time, money or risk.

==============================================================================

[21.10] But my problem doesn't have anything to do with circles and ellipses,
        so what good is that silly example to me?

Ahhh, there's the rub.  You think the Circle/Ellipse example is just a silly
example.  But in reality, your problem is an isomorphism to that example.

I don't care what your inheritance problem is, but all (yes all) bad
inheritances boil down to the Circle-is-not-a-kind-of-Ellipse example.

Here's why: Bad inheritances always have a base class with an extra capability
(often an extra member function or two; sometimes an extra promise made by one
or a combination of member functions) that a derived class can't satisfy.
You've either got to make the base class weaker, make the derived class
stronger, or eliminate the proposed inheritance relationship.  I've seen lots
and lots and lots of these bad inheritance proposals, and believe me, they all
boil down to the Circle/Ellipse example.

Therefore, if you truly understand the Circle/Ellipse example, you'll be able
to recognize bad inheritance everywhere.  If you don't understand what's going
on with the Circle/Ellipse problem, the chances are high that you'll make some
very serious and very expensive inheritance mistakes.

Sad but true.

(Note: this FAQ has to do with public inheritance; private and protected
inheritance[24] are different.)

==============================================================================

[21.11] If SortedList has exactly the same public interface as List, is
        SortedList a kind-of List? [NEW!]

[Recently created thanks to a conversation with Dave Mikesell (in 6/02).]

Probably not.

The most important insight is that the answer depends on the details of the
base class's contract.  It is not enough to know that the public interfaces
are
compatible; one also needs to know if the contracts are compatible.

The important word in the previous sentence is "contract." That word goes well
beyond the public interface = method signatures = method names and parameter
types and constness.  A method's contract means its advertised behavior =
advertised requirements and promises = advertised preconditions and
postconditions.  So if the base class has a method void insert(const Foo& x),
the contract of that method includes the signature (meaning the name insert
and
the parameter const Foo&), but goes well beyond that to include the advertised
behavior of that method.

The other important word is advertised.  The intention here is to
differentiate
between the code inside the method (assuming the base class's method has code;
i.e., assuming it's not an unimplemented pure virtual function) and the
promises made outside the method.  This is where things get tricky.  Suppose
the insert(const Foo& x) method inserts a copy of x at the end of this List,
and the override of that method in SortedList inserts x in the proper
sort-order.  Even though the override behaves in a way that is incompatible
with the base class's code, the inheritance might still be proper if the base
class makes a "weak" or "adaptable" promise.  For example, if the advertised
promise of the base class's insert(const Foo& x) method is something vague
like, "Promises a copy of x will be inserted somewhere within this List," then
the inheritance is probably okay since the override abides by the advertised
behavior even though it is incompatible with the implemented behavior.

The derived class needs to do what the base class promises to do, not     what
it actually does.

The key is that we've separated the advertised behavior ("specification") from
implemented behavior ("implementation"), and we rely on the specification
rather than the implementation.  This is very important because in a large
percentage of the cases the base class's method is an unimplemented pure
virtual -- the only thing that can be relied on is the specification -- there
simply is no implementation on which to rely.

Back to SortedList and List: it seems likely that List has one or more methods
that have contracts which guarantee order, and therefore SortedList is
probably
not a kind-of List.  For example, if List has a method that lets you reorder
things, prepend things, append things, or change the ith element, and if those
methods make the typical advertised promise, then SortedList would need to
violate that advertised behavior and the inheritance would be improper.  But
it
all depends on what the base class advertises -- on the base class's contract.

==============================================================================

SECTION [22]: Inheritance -- abstract base classes (ABCs)


[22.1] What's the big deal of separating interface from implementation?

Interfaces are a company's most valuable resources.  Designing an interface
takes longer than whipping together a concrete class which fulfills that
interface.  Furthermore interfaces require the time of more expensive people.

Since interfaces are so valuable, they should be protected from being
tarnished
by data structures and other implementation artifacts.  Thus you should
separate interface from implementation.

==============================================================================

[22.2] How do I separate interface from implementation in C++ (like Modula-2)?

Use an ABC[22.3].

==============================================================================

[22.3] What is an ABC?

An abstract base class.

At the design level, an abstract base class (ABC) corresponds to an abstract
concept.  If you asked a mechanic if he repaired vehicles, he'd probably
wonder
what kind-of vehicle you had in mind.  Chances are he doesn't repair space
shuttles, ocean liners, bicycles, or nuclear submarines.  The problem is that
the term "vehicle" is an abstract concept (e.g., you can't build a "vehicle"
unless you know what kind of vehicle to build).  In C++, class Vehicle would
be
an ABC, with Bicycle, SpaceShuttle, etc, being derived classes (an OceanLiner
is-a-kind-of-a Vehicle).  In real-world OO, ABCs show up all over the place.

At the programming language level, an ABC is a class that has one or more pure
virtual[22.4] member functions.  You cannot make an object (instance) of an
ABC.

==============================================================================

[22.4] What is a "pure virtual" member function?

A member function declaration that turns a normal class into an abstract class
(i.e., an ABC).  You normally only implement it in a derived class.

Some member functions exist in concept; they don't have any reasonable
definition.  E.g., suppose I asked you to draw a Shape at location (x,y) that
has size 7.  You'd ask me "what kind of shape should I draw?" (circles,
squares, hexagons, etc, are drawn differently).  In C++, we must indicate the
existence of the draw() member function (so users can call it when they have a
Shape* or a Shape&), but we recognize it can (logically) be defined only in
derived classes:

 class Shape {
 public:
   virtual void draw() const = 0;  // = 0 means it is "pure virtual"
   // ...
 };

This pure virtual function makes Shape an ABC.  If you want, you can think of
the "= 0;" syntax as if the code were at the NULL pointer.  Thus Shape
promises
a service to its users, yet Shape isn't able to provide any code to fulfill
that promise.  This forces any actual object created from a [concrete] class
derived from Shape to have the indicated member function, even though the base
class doesn't have enough information to actually define it yet.

Note that it is possible to provide a definition for a pure virtual function,
but this usually confuses novices and is best avoided until later.

==============================================================================

[22.5] How do you define a copy constructor or assignment operator for a class
       that contains a pointer to a (abstract) base class?

If the class "owns" the object pointed to by the (abstract) base class pointer,

use the Virtual Constructor Idiom[20.6] in the (abstract) base class.  As
usual
with this idiom, we declare a pure virtual[22.4] clone() method in the base
class:

 class Shape {
 public:
   // ...
   virtual Shape* clone() const = 0;   // The Virtual (Copy) Constructor[20.6]
   // ...
 };

Then we implement this clone() method in each derived class:

 class Circle : public Shape {
 public:
   // ...
   virtual Shape* clone() const { return new Circle(*this); }
   // ...
 };

 class Square : public Shape {
 public:
   // ...
   virtual Shape* clone() const { return new Square(*this); }
   // ...
 };

Now suppose that each Fred object "has-a" Shape object.  Naturally the Fred
object doesn't know whether the Shape is Circle or a Square or ...  Fred's
copy
constructor and assignment operator will invoke Shape's clone() method to copy
the object:

 class Fred {
 public:
   Fred(Shape* p) : p_(p) { assert(p != NULL); }   // p must not be NULL
  ~Fred() { delete p_; }
   Fred(const Fred& f) : p_(f.p_->clone()) { }
   Fred& operator= (const Fred& f)
     {
       if (this != &f) {              // Check for self-assignment
         Shape* p2 = f.p_->clone();   // Create the new one FIRST...
         delete p_;                   // ...THEN delete the old one
         p_ = p2;
       }
       return *this;
     }
   // ...
 private:
   Shape* p_;
 };

==============================================================================

SECTION [23]: Inheritance -- what your mother never told you


[23.1] Is it okay for a non-virtual function of the base class to call a
       virtual function?

Yes.  It's sometimes (not always!) a great idea.  For example, suppose all
Shape objects have a common algorithm for printing, but this algorithm depends
on their area and they all have a potentially different way to compute their
area.  In this case Shape's area() method would necessarily have to be virtual
(probably pure virtual) but Shape::print() could, if we were guaranteed no
derived class wanted a different algorithm for printing[23.4], be a
non-virtual
defined in the base class Shape.

 #include "Shape.hpp"

 void Shape::print() const
 {
     float a = this->area();  // area() is pure virtual
     // ...
 }

==============================================================================

[23.2] That last FAQ confuses me.  Is it a different strategy from the other
       ways to use virtual functions? What's going on?

Yes, it is a different strategy.  Yes, there really are two different basic
ways to use virtual functions:

 1. Suppose you have the situation described in the previous FAQ: you have a
    method whose overall structure is the same for each derived class, but has
    little pieces that are different in each derived class.  So the algorithm
    is the same, but the primitives are different.  In this case you'd write
    the overall algorithm in the base class as a public method (that's
    sometimes non-virtual), and you'd write the little pieces in the derived
    classes.  The little pieces would be declared in the base class (they're
    often protected, they're often pure virtual, and they're certainly
    virtual), and they'd ultimately be defined in each derived class.  The
most
    critical question in this situation is whether or not the public method
    containing the overall algorithm should be virtual.  The answer is to make
    it virtual if you think that some derived class might need to override
    it[23.4].

 2. Suppose you have the exact opposite situation from the previous FAQ, where
    you have a method whose overall structure is different in each derived
    class, yet it has little pieces that are the same in most (if not all)
    derived classes.  In this case you'd put the overall algorithm in a public
    virtual that's ultimately defined in the derived classes, and the little
    pieces of common code can be written once (to avoid code duplication) and
    stashed somewhere (anywhere!).  A common place to stash the little pieces
    is in the protected part of the base class, but that's not necessary and
it
    might not even be best.  Just find a place to stash them and you'll be
    fine.  Note that if you do stash them in the base class, you should
    normally make them protected, since normally they do things that public
    users don't need/want to do.  Assuming they're protected, they probably
    shouldn't be virtual: if the derived class doesn't like the behavior in
one
    of them, it doesn't have to call that method.

For emphasis, the above list is a both/and situation, not an either/or
situation.  In other words, you don't have to choose between these two
strategies on any given class.  It's perfectly normal to have method f()
correspond to strategy #1 while method g() corresponds to strategy #2.  In
other words, it's perfectly normal to have both strategies working in the same
class.

==============================================================================

[23.3] When my base class's constructor calls a virtual function, why doesn't
       my derived class's override of that virtual function get invoked?

During the class Base's constructor, the object isn't yet a Derived, so if
Base::Base() calls a virtual[20] function virt(), the Base::virt() will be
invoked, even if Derived::virt() exists.

Similarly, during Base's destructor, the object is no longer a Derived, so
when
Base::~Base() calls virt(), Base::virt() gets control, not the Derived::virt()
override.

You'll quickly see the wisdom of this approach when you imagine the disaster
if
Derived::virt() touched a member object from class Derived.  In particular, if
Base::Base() called the virtual function virt(), this rule causes Base::virt()
to be invoked.  If it weren't for this rule, Derived::virt() would get called
before the Derived part of a Derived object is constructed, and Derived::
virt()
could touch unconstructed member objects from the Derived part of a Derived
object.  That would be a disaster.

==============================================================================

[23.4] Should a derived class replace ("override") a non-virtual function from
       a base class?

It's legal, but it ain't moral.

Experienced C++ programmers will sometimes redefine a non-virtual function for
efficiency (e.g., if the derived class implementation can make better use of
the derived class's resources) or to get around the hiding rule[23.5].
However
the client-visible effects must be identical, since non-virtual functions are
dispatched based on the static type of the pointer/reference rather than the
dynamic type of the pointed-to/referenced object.

==============================================================================

[23.5] What's the meaning of, Warning: Derived::f(float) hides Base::f(int)?

It means you're going to die.

Here's the mess you're in: if Base declares a member function f(int), and
Derived declares a member function f(float) (same name but different parameter
types and/or constness), then the Base f(int) is "hidden" rather than
"overloaded" or "overridden" (even if the Base f(int) is virtual[20]).

Here's how you get out of the mess: Derived must have a using declaration of
the hidden member function.  For example,

 class Base {
 public:
   void f(int);
 };

 class Derived : public Base {
 public:
   using Base::f;    // This un-hides Base::f(int)
   void f(double);
 };

If the using syntax isn't supported by your compiler, redefine the hidden Base
member function(s), even if they are non-virtual[23.4].  Normally this
re-definition merely calls the hidden Base member function using the :: syntax.

E.g.,

 class Derived : public Base {
 public:
   void f(double);
   void f(int i) { Base::f(i); }  // The redefinition merely calls Base::
f(int)
 };

==============================================================================

[23.6] What does it mean that the "virtual table" is an unresolved external?

If you get a link error of the form
"Error: Unresolved or undefined symbols detected: virtual table for class Fred,
"
you probably have an undefined virtual[20] member function in class Fred.

The compiler typically creates a magical data structure called the "virtual
table" for classes that have virtual functions (this is how it handles dynamic
binding[20.2]).  Normally you don't have to know about it at all.  But if you
forget to define a virtual function for class Fred, you will sometimes get
this
linker error.

Here's the nitty gritty: Many compilers put this magical "virtual table" in
the
compilation unit that defines the first non-inline virtual function in the
class.  Thus if the first non-inline virtual function in Fred is wilma(), the
compiler will put Fred's virtual table in the same compilation unit where it
sees Fred::wilma().  Unfortunately if you accidentally forget to define
Fred::wilma(), rather than getting a Fred::wilma() is undefined, you may get a
"Fred's virtual table is undefined".  Sad but true.

==============================================================================

[23.7] How can I set up my class so it won't be inherited from?

This is known as making the class "final" or "a leaf." There are two ways to
do
it: an easy technical approach and an even easier non-technical approach.
 * The (easy) technical approach is to make the class's constructors private
   and to use the Named Constructor Idiom[10.8] to create the objects.  No one
   can create objects of a derived class since the base class's constructor
   will be inaccessible.  The "named constructors" themselves could return by
   pointer if you want your objects allocated by new[16.20] or they could
   return by value if you want the objects created on the stack[10.8].
 * The (even easier) non-technical approach is to put a big fat ugly comment
   next to the class definition.  The comment could say, for example,
   // We'll fire you if you inherit from this class or even just
   /*final*/ class Whatever {...};.  Some programmers balk at this because it
   is enforced by people rather than by technology, but don't knock it on face
   value: it is quite effective in practice.

==============================================================================

[23.8] How can I set up my member function so it won't be overridden in a
       derived class?

This is known as making the method "final" or "a leaf." Here's an easy-to-use
solution to this that gives you 90+% of what you want: simply add a comment
next to the method and rely on code reviews or random maintenance activities
to
find violators.  The comment could say, for example, // We'll fire you if you
override this method or perhaps more likely, /*final*/ void theMethod();.

The advantages to this technique are (a) it is extremely easy/fast/inexpensive
to use, and (b) it is quite effective in practice.  In other words, you get
90+% of the benefit with almost no cost -- lots of bang per buck.

(I'm not aware of a "100% solution" to this problem so this may be the best
you
can get.  If you know of something better, please feel free to email me.  But
please do not email me objecting to this solution because it's low-tech or
because it doesn't "prevent" people from doing the wrong thing.  Who cares
whether it's low-tech or high-tech as long as it's effective?!? And nothing in
C++ "prevents" people from doing the wrong thing.  Using pointer casts[26.10]
and pointer arithmetic, people can do just about anything they want.  C++
makes
it easy to do the right thing, but it doesn't prevent espionage[16.24].
Besides, the original question (see above) asked for something so people won't
do the wrong thing, not so they can't do the wrong thing.)

In any case, this solution should give you most of the potential benefit at
almost no cost.

==============================================================================

SECTION [24]: Inheritance -- private and protected inheritance


[24.1] How do you express "private inheritance"?

When you use : private instead of : public.  E.g.,

 class Foo : private Bar {
 public:
   // ...
 };

==============================================================================

[24.2] How are "private inheritance" and "composition" similar?

private inheritance is a syntactic variant of composition (AKA aggregation
and/or has-a).

E.g., the "Car has-a Engine" relationship can be expressed using simple
composition:

 class Engine {
 public:
   Engine(int numCylinders);
   void start();                 // Starts this Engine
 };

 class Car {
 public:
   Car() : e_(8) { }             // Initializes this Car with 8 cylinders
   void start() { e_.start(); }  // Start this Car by starting its Engine
 private:
   Engine e_;                    // Car has-a Engine
 };

The "Car has-a Engine" relationship can also be expressed using private
inheritance:

 class Car : private Engine {    // Car has-a Engine
 public:
   Car() : Engine(8) { }         // Initializes this Car with 8 cylinders
   using Engine::start;          // Start this Car by starting its Engine
 };

There are several similarities between these two variants:
 * In both cases there is exactly one Engine member object contained in every
   Car object
 * In neither case can users (outsiders) convert a Car* to an Engine*
 * In both cases the Car class has a start() method that calls the start()
   method on the contained Engine object.

There are also several distinctions:
 * The simple-composition variant is needed if you want to contain several
   Engines per Car
 * The private-inheritance variant can introduce unnecessary multiple
   inheritance
 * The private-inheritance variant allows members of Car to convert a Car* to
   an Engine*
 * The private-inheritance variant allows access to the protected members of
   the base class
 * The private-inheritance variant allows Car to override Engine's virtual[20]
   functions
 * The private-inheritance variant makes it slightly simpler (20 characters
   compared to 28 characters) to give Car a start() method that simply calls
   through to the Engine's start() method

Note that private inheritance is usually used to gain access into the
protected
members of the base class, but this is usually a short-term solution
(translation: a band-aid[24.3]).

==============================================================================

[24.3] Which should I prefer: composition or private inheritance?

Use composition when you can, private inheritance when you have to.

Normally you don't want to have access to the internals of too many other
classes, and private inheritance gives you some of this extra power (and
responsibility).  But private inheritance isn't evil; it's just more expensive
to maintain, since it increases the probability that someone will change
something that will break your code.

A legitimate, long-term use for private inheritance is when you want to build
a
class Fred that uses code in a class Wilma, and the code from class Wilma
needs
to invoke member functions from your new class, Fred.  In this case, Fred
calls
non-virtuals in Wilma, and Wilma calls (usually pure virtuals[22.4]) in itself,

which are overridden by Fred.  This would be much harder to do with
composition.

 class Wilma {
 protected:
   void fredCallsWilma()
     {
       std::cout << "Wilma::fredCallsWilma()\n";
       wilmaCallsFred();
     }
   virtual void wilmaCallsFred() = 0;   // A pure virtual function[22.4]
 };

 class Fred : private Wilma {
 public:
   void barney()
     {
       std::cout << "Fred::barney()\n";
       Wilma::fredCallsWilma();
     }
 protected:
   virtual void wilmaCallsFred()
     {
       std::cout << "Fred::wilmaCallsFred()\n";
     }
 };

==============================================================================

[24.4] Should I pointer-cast from a private derived class to its base class?

Generally, No.

From a member function or friend[14] of a privately derived class, the
relationship to the base class is known, and the upward conversion from
PrivatelyDer* to Base* (or PrivatelyDer& to Base&) is safe; no cast is needed
or recommended.

However users of PrivatelyDer should avoid this unsafe conversion, since it is
based on a private decision of PrivatelyDer, and is subject to change without
notice.

==============================================================================

[24.5] How is protected inheritance related to private inheritance?

Similarities: both allow overriding virtual[20] functions in the
private/protected base class, neither claims the derived is a kind-of its base.


Dissimilarities: protected inheritance allows derived classes of derived
classes to know about the inheritance relationship.  Thus your grand kids are
effectively exposed to your implementation details.  This has both benefits
(it
allows derived classes of the protected derived class to exploit the
relationship to the protected base class) and costs (the protected derived
class can't change the relationship without potentially breaking further
derived classes).

Protected inheritance uses the : protected syntax:

 class Car : protected Engine {
 public:
   // ...
 };

==============================================================================

[24.6] What are the access rules with private and protected inheritance?

Take these classes as examples:

 class B                    { /*...*/ };
 class D_priv : private   B { /*...*/ };
 class D_prot : protected B { /*...*/ };
 class D_publ : public    B { /*...*/ };
 class UserClass            { B b; /*...*/ };

None of the derived classes can access anything that is private in B.  In
D_priv, the public and protected parts of B are private.  In D_prot, the
public
and protected parts of B are protected.  In D_publ, the public parts of B are
public and the protected parts of B are protected (D_publ is-a-kind-of-a B).
class UserClass can access only the public parts of B, which "seals off"
UserClass from B.

To make a public member of B so it is public in D_priv or D_prot, state the
name of the member with a B:: prefix.  E.g., to make member B::f(int,float)
public in D_prot, you would say:

 class D_prot : protected B {
 public:
   using B::f;  // Note: Not using B::f(int,float)
 };

==============================================================================


--
   mm       ★__      __  __ __★______ ______ __  __★
/^(  )^\      █      █  █/    █____ █__█ █∨█
\,(..),/  ▅__█  ▅__█  █\__  ▂__█ █  █ █  █
  V~~V   ▇▆▅▃▁I'm a bat. I'm very bad!^Q^_▃▄▆▇ 你好!^_^欢迎大家到linux

※ 修改:·jjksam 於 Jul  1 14:43:32 修改本文·[FROM: 192.168.0.146]
※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.0.146]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店