|














Am avut 371717 vizite de la lansarea siteului.

|
|
Inapoi
Inainte
Cuprins
Composition vs. inheritance
(revisited)
One of the clearest ways to determine
whether you should be using composition or inheritance is by asking whether
you’ll ever need to upcast from your new class. Earlier in this chapter,
the Stack class was specialized using inheritance. However, chances are
the StringStack objects will be used only as string containers and
never upcast, so a more appropriate alternative is composition:
//: C14:InheritStack2.cpp
// Composition vs. inheritance
#include "../C09/Stack4.h"
#include "../require.h"
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class StringStack {
Stack stack; // Embed instead of inherit
public:
void push(string* str) {
stack.push(str);
}
string* peek() const {
return (string*)stack.peek();
}
string* pop() {
return (string*)stack.pop();
}
};
int main() {
ifstream in("InheritStack2.cpp");
assure(in, "InheritStack2.cpp");
string line;
StringStack textlines;
while(getline(in, line))
textlines.push(new string(line));
string* s;
while((s = textlines.pop()) != 0) // No cast!
cout << *s << endl;
} ///:~
The file is identical to
InheritStack.cpp, except that a Stack object is embedded in
StringStack, and member functions are called for the embedded object.
There’s still no time or space overhead because the subobject takes up the
same amount of space, and all the additional type checking happens at compile
time.
Although it tends to be more confusing,
you could also use private inheritance to express “implemented in
terms of.” This would also solve the problem adequately. One place it
becomes important, however, is when multiple
inheritance might be warranted.
In that case, if you see a design in which composition can be used instead of
inheritance, you may be able to eliminate the need for multiple
inheritance.
Pointer & reference
upcasting
In Instrument.cpp, the upcasting
occurs during the function call – a Wind object outside the
function has its reference taken and becomes an Instrument reference
inside the function. Upcasting can also occur during a simple assignment to a
pointer or reference:
Wind w;
Instrument* ip = &w; // Upcast
Instrument& ir = w; // Upcast
Like the function call, neither of these
cases requires an explicit
cast.
A crisis
Of course, any
upcast loses type information about an object. If you
say
Wind w;
Instrument* ip = &w;
the compiler can deal with ip
only as an Instrument pointer and nothing else. That is, it
cannot know that ip actually happens to point to a Wind
object. So when you call the play( ) member function by saying
ip->play(middleC);
the compiler can know only that
it’s calling play( ) for an Instrument pointer, and
call the base-class version of Instrument::play( ) instead of what
it should do, which is call Wind::play( ). Thus, you won’t get
the correct behavior.
This is a significant problem; it is
solved in Chapter 15 by introducing the third cornerstone of object-oriented
programming: polymorphism (implemented in C++ with virtual
functions).
Summary
Both inheritance and composition allow
you to create a new type from existing types, and both embed subobjects of the
existing types inside the new type. Typically, however, you use composition to
reuse existing types as part of the underlying implementation of the new type
and inheritance when you want to force the new type to be the same type as the
base class (type equivalence guarantees interface equivalence). Since the
derived class has the base-class interface, it can be upcast to the base,
which is critical for polymorphism as you’ll see in Chapter
15.
Although code reuse through composition
and inheritance is very helpful for rapid project development, you’ll
generally want to redesign your class hierarchy before allowing other
programmers to become dependent on it. Your goal is a hierarchy in which each
class has a specific use and is neither too big (encompassing so much
functionality that it’s unwieldy to reuse) nor annoyingly small (you
can’t use it by itself or without adding
functionality).
Exercises
Solutions to selected exercises
can be found in the electronic document The Thinking in C++ Annotated
Solution Guide, available for a small fee from www.BruceEckel.com.
- Modify Car.cpp so
that it also inherits from a class called Vehicle, placing appropriate
member functions in Vehicle (that is, make up some member functions). Add
a nondefault constructor to Vehicle, which you must call inside
Car’s
constructor.
- Create
two classes, A and B, with default constructors that announce
themselves. Inherit a new class called C from A, and create a
member object of B in C, but do not create a constructor for
C. Create an object of class C and observe the
results.
- Create a
three-level hierarchy of classes with default constructors, along with
destructors, both of which announce themselves to cout. Verify
that for an object of the most derived type, all three constructors and
destructors are automatically called. Explain the order in which the calls are
made.
- Modify
Combined.cpp to add another level of inheritance and a new member object.
Add code to show when the constructors and destructors are being
called.
- In
Combined.cpp, create a class D that inherits from B and has
a member object of class C. Add code to show when the constructors and
destructors are being
called.
- Modify
Order.cpp to add another level of inheritance Derived3 with member
objects of class Member4 and Member5. Trace the output of the
program.
- In
NameHiding.cpp, verify that in Derived2, Derived3, and
Derived4, none of the base-class versions of f( ) are
available.
- Modify
NameHiding.cpp by adding three overloaded functions named
h( ) to Base, and show that redefining one of them in a
derived class hides the
others.
- Inherit a
class StringVector from vector<void*> and redefine the
push_back( ) and operator[] member functions to accept and
produce string*. What happens if you try to push_back( ) a
void*?
- Write
a class containing a long and use the psuedo-constructor call syntax in
the constructor to initialize the
long.
- Create
a class called Asteroid. Use inheritance to specialize the
PStash class in Chapter 13 (PStash.h & PStash.cpp) so
that it accepts and returns Asteroid pointers. Also modify
PStashTest.cpp to test your classes. Change the class so PStash is
a member
object.
- Repeat
Exercise 11 with a vector instead of a
PStash.
- In
SynthesizedFunctions.cpp, modify Chess to give it a default
constructor, copy-constructor, and assignment operator. Demonstrate that
you’ve written these
correctly.
- Create
two classes called Traveler and Pager without default
constructors, but with constructors that take an argument of type string,
which they simply copy to an internal string variable. For each class,
write the correct copy-constructor and assignment operator. Now inherit a class
BusinessTraveler from Traveler and give it a member object of type
Pager. Write the correct default constructor, a constructor that takes a
string argument, a copy-constructor, and an assignment
operator.
- Create a
class with two static member functions. Inherit from this class and
redefine one of the member functions. Show that the other is hidden in the
derived class.
- Look
up more of the member functions for ifstream. In FName2.cpp, try
them out on the file
object.
- Use
private and protected inheritance to create two new classes from a
base class. Then attempt to upcast objects of the derived class to the base
class. Explain what
happens.
- In
Protected.cpp, add a member function in Derived that calls the
protected Base member
read( ).
- Change
Protected.cpp so that Derived is using protected
inheritance. See if you can call value( ) for a Derived
object.
- Create a
class called SpaceShip with a fly( ) method. Inherit
Shuttle from SpaceShip and add a land( ) method.
Create a new Shuttle, upcast by pointer or reference to a
SpaceShip, and try to call the land( ) method. Explain the
results.
- Modify
Instrument.cpp to add a prepare( ) method to
Instrument. Call prepare( ) inside
tune( ).
- Modify
Instrument.cpp so that play( ) prints a message to
cout, and Wind redefines play( ) to print a different
message to cout. Run the program and explain why you probably
wouldn’t want this behavior. Now put the virtual keyword (which you
will learn about in Chapter 15) in front of the play( ) declaration
in Instrument and observe the change in the
behavior.
- In
CopyConstructor.cpp, inherit a new class from Child and give it a
Member m. Write a proper constructor,
copy-constructor, operator=, and operator<< for
ostreams, and test the class in
main( ).
- Take
the example CopyConstructor.cpp and modify it by adding your own
copy-constructor to Child without calling the base-class
copy-constructor and see what happens. Fix the problem by making a proper
explicit call to the base-class copy constructor in the constructor-initializer
list of the Child
copy-constructor.
- Modify
InheritStack2.cpp to use a vector<string> instead of a
Stack.
- Create
a class Rock with a default constructor, a copy-constructor, an
assignment operator, and a destructor, all of which announce to cout that
they’ve been called. In main( ), create a
vector<Rock> (that is, hold Rock objects by value) and add
some Rocks. Run the program and explain the output you get. Note whether
the destructors are called for the Rock objects in the vector. Now
repeat the exercise with a vector<Rock*>. Is it possible to create
a
vector<Rock&>?
- This
exercise creates the design pattern called proxy. Start with a base class
Subject and give it three functions: f( ), g( ),
and h( ). Now inherit a class Proxy and two classes
Implementation1 and Implementation2 from Subject.
Proxy should contain a pointer to a Subject, and all the member
functions for Proxy should just turn around and make the same calls
through the Subject pointer. The Proxy constructor takes a pointer
to a Subject that is installed in the Proxy (usually by the
constructor). In main( ), create two different Proxy objects
that use the two different implementations. Now modify Proxy so that you
can dynamically change
implementations.
- Modify
ArrayOperatorNew.cpp from Chapter 13 to show that, if you inherit from
Widget, the allocation still works correctly. Explain why inheritance in
Framis.cpp from Chapter 13 would not work
correctly.
- Modify
Framis.cpp from Chapter 13 by inheriting from Framis and creating
new versions of new and delete for your derived class. Demonstrate
that they work
correctly.
[51]
In Java, the compiler won’t let you decrease the access of a member during
inheritance.
[52]
To learn more about this idea, see Extreme Programming Explained, by Kent
Beck (Addison-Wesley 2000).
[53]
See Refactoring: Improving the Design of Existing Code by Martin Fowler
(Addison-Wesley 1999).
 |
| |
|