A superclass, also called a base class, is a class from which a derived class inherits members and behavior. An object of the derived class combines the data parts of both classes, so operations must maintain every part of the hierarchy. section 8.7
Treat a superclass as part of the derived object's correctness boundary:
- Call the superclass assignment operator when implementing assignment in a derived class; otherwise, private superclass data is left unchanged. section 8.7
- Give a polymorphic superclass a virtual destructor so deleting a derived object through a superclass pointer destroys both parts. section 8.8
- Design a superclass with its future derived classes in mind, because errors higher in the hierarchy affect every subtype. section 8.8
Assignment that silently preserves the wrong name
A Student derives from Person. If Student::operator= copies only gpa, assigning Thelma to Louise produces two students named Thelma and leaves Louise's inherited name unchanged. Calling Person::operator=(o) before copying gpa updates both class parts. section 8.7
Listing 8.13 A derived class copying its data but disregarding its base class
class Person {
private:
std::string name;
public:
Person(const std::string& name) : name(name) {}
const std::string& getName() { return name; }
};
class Student : public Person {
private:
double gpa;
public:
Student(const std::string& name, double gpa) : Person(name), gpa(gpa) {}
Student& operator=(const Student& o) {
if (this == &o)
return *this;
gpa = o.gpa; #1
return *this;
}
double getGpa() const { return gpa; }
};
int main() {
Student thelma("Thelma", 3.85);
Student louise("Louise", 3.75);
louise = thelma;
std::cout << thelma.getName() << ' ' << thelma.getGpa() << '\n';
std::cout << louise.getName() << ' ' << louise.getGpa() << '\n';
}
Inspect the faulty derived assignment operator and its output before comparing it with the corrected superclass assignment. section 8.7
Listing 8.14 The derived class properly considering its base class
class Person {
private:
std::string name;
public:
Person(const std::string& name) : name(name) {}
const std::string& getName() { return name; }
};
class Student : public Person {
private:
double gpa;
public:
Student(const std::string& name, double gpa) : Person(name), gpa(gpa) {}
Student& operator=(const Student& o) {
if (this == &o)
return *this;
Person::operator=(o); #1
gpa = o.gpa; #2
return *this;
}
double getGpa() const { return gpa; }
};
int main() {
Student thelma("Thelma", 3.85);
Student louise("Louise", 3.75);
louise = thelma;
std::cout << thelma.getName() << ' ' << thelma.getGpa() << '\n';
std::cout << louise.getName() << ' ' << louise.getGpa() << '\n';
}
Compare the corrected assignment operator, which explicitly delegates inherited state to Person before copying Student state. section 8.7
Polymorphic deletion
Shape is a superclass of Square and declares virtual area(). Deleting a Square through Shape* is undefined when Shape lacks a virtual destructor; making the destructor virtual produces ~Square followed by ~Shape. section 8.8
Listing 8.15 Base class without a virtual destructor
class Point {
private:
double x;
double y;
public:
Point(double x, double y) : x(x), y(y) {}
};
class Shape {
private:
Point* location;
public:
Shape(double x, double y) : location(new Point(x, y)) {}
~Shape() { delete location; std::cout << "~Shape\n"; } #1
virtual double area() const = 0; #2
};
class Square : public Shape {
private:
double* side;
public:
Square(double side, double x, double y) : Shape(x, y),
side(new double(side)) {}
~Square() { delete side; std::cout << "~Square\n"; }
double area() const { return *side * *side; } // looks weird
};
int main() {
Shape* s = new Square(2.5, 0, 0); // place at origin
std::cout << s->area() << '\n';
delete s; #3
return 0;
}
Examine the failure caused by deleting a dynamically allocated Square through a Shape pointer without a virtual superclass destructor. section 8.8
Listing 8.16 A base class with virtual destructor
class Point {
private:
double x;
double y;
public:
Point(double x, double y) : x(x), y(y) {}
};
class Shape {
private:
Point* location;
public:
Shape(double x, double y) : location(new Point(x, y)) {}
virtual ~Shape() { delete location; #1
std::cout << "~Shape\n"; }
virtual double area() const = 0;
};
class Square : public Shape {
private:
double* side;
public:
Square(double side, double x, double y) : Shape(x, y),
side(new double(side)) {}
~Square() { delete side; std::cout << "~Square\n"; }
double area() const { return *side * *side; } // still looks weird
};
int main() {
Shape* s = new Square(2.5, 0, 0); // place at origin
std::cout << s->area() << '\n';
delete s;
return 0;
}
Compare the safer Shape definition, where a virtual destructor enables derived and superclass cleanup. section 8.8









