📚 Gaddis (Ch. 15)
Establishes an “is-a” relationship:
… Infinite examples: The real world works this way!
Base class (or parent): the more general class… the “starting point”.
Derived class (or child): more specialized, derived from base class.
Notation:
Base and derived class headers.
The derived class object “is a(n)” object of the base class.
Has all characteristics of base class!
Derived class objects can “use” (access):
public
or protected
members of base class.The physical object for a derived class object includes all parts of the base class, even though the private
base class members are not directly accessible to derived class methods.
protected
?protected
: access specifier, similar to private
protected
members can be accessed by derived classes!
private
data from base class is part of derived class, but cannot be accessed.You may also inherit with a selected maximum level of access:
public
protected
public
items from base class become protected
in derived class.private
public
and protected
items from base class become private
in derived class.Diagram of effect of access specifiers.
Base class must be constructed before derived class.
Derived class must be destructed before the base class.
Derived class may pass arguments to base class constructor:
You must use the constructor’s member initialization list for this.
Detailed Example: A Square by inheriting from a Rectangle
A Square is just a rectangle with the additional constraint that the width and height are identical.
class Rectangle{
public:
Rectangle(int w, int h) : width{w}, height{h} {}
int get_width() const;
int get_height() const;
int get_area() const;
void print() const;
void set_width(int w);
void set_height(int w);
private:
int width;
int height;
};
class Square : public Rectangle{
public:
Square(int s) : Rectangle{s, s} {}
};
Simple! But, it leads to an issue.
Simple! But, it leads to an issue.
We inherited by public
from Rectangle
, so we can use any Rectangle
method.
Bad news - this lets us make a mistake by setting width/height independently.
How can we fix this problem?
What if we define a “safe” way to set the side length?
class Square : public Rectangle{
public:
Square(int s) : Rectangle{s, s} {}
void set_side_length(int s) {
set_width(s);
set_height(s);
}
};
What if we define a “safe” way to set the side length?
class Square : public Rectangle{
public:
Square(int s) : Rectangle{s, s} {}
void set_side_length(int s) {
set_width(s);
set_height(s);
}
};
Square s1{3}; // 3x3 square
s1.print(); // "w=3, h=3"
s1.set_side_length(4)
s1.print(); // "w=4, h=4" Better...
s1.set_width(5);
s1.print(); // "w=5, h=4" ERROR STILL!
The new method allows us to do the right thing, but it does not prevent us from doing the wrong thing. We can do better…
What if we redefine the behavior of the set
methods for width/height?
class Square : public Rectangle{
public:
Square(int s) : Rectangle{s, s} {}
void set_side_length(int s); // sets both width and height
void set_width(int w) { set_side_length(w); }
void set_height(int h) { set_side_length(h); }
};
Square s1{3}; // 3x3 square
s1.print(); // "w=3, h=3"
s1.set_side_length(4)
s1.print(); // "w=4, h=4" Better...
s1.set_width(5);
s1.print(); // "w=5, h=5" FIXED!
Now, we are able to interact with the object in any way we expect, but it will always do the “right” thing. It prevents us from ending up in an inconsistent state.
class Square : public Rectangle{
public:
Square(int s) : Rectangle{s, s} {}
void set_side_length(int s); // sets both width and height
void set_width(int w) { set_side_length(w); }
void set_height(int h) { set_side_length(h); }
};
What are the benefits / problems of this solution?
What about aggregation?
What about protected
inheritance?
What about protected
inheritance?
class Square : protected Rectangle{
public:
Square(int s) : Rectangle{s, s} {}
int get_side_length() const { return get_width(); }
int get_area() const { return Rectangle::get_area(); }
void print() const { Rectangle::print(); }
void set_side_length(int s) {
set_width(s);
set_height(s);
}
};
Forces us to redefine any methods we want in our public interface…
But if the overlap isn’t too large, that should not be too many.
Also, the redefined methods are mostly simple “pass-through” methods.
class Square : protected Rectangle{
public:
Square(int s) : Rectangle{s, s} {}
int get_side_length() const;
int get_area() const;
void print() const;
void set_side_length(int s);
};
Are there any remaining problems?
Maybe print()
needs to be customized:
There might still be a monster lurking in the bushes…
But we’ll see more about that soon…
Inheritance
CS 50x2 Accelerated Programming Series