Abstract Classes
Let’s take another look at what we wrote earlier:
class Animal {
public:
Animal(string name) : _name(name) {}
virtual void bark() {}
protected:
string _name;
};
class Cat : public Animal {
public:
Cat(string name) : Animal(name) {}
void bark() {
cout << _name << "Meow!" << endl;
}
};
class Dog : public Animal {
public:
Dog(string name) : Animal(name) {}
void bark() {
cout << _name << "Woof!" << endl;
}
};
class Bear : public Animal {
public:
Bear(string name) : Animal(name) {}
void bark() {
cout << _name << "Rua!" << endl;
}
};
Remember that the original intention of defining class Animal is not to make Animal an abstract of some entities, but to:
- Let all derived classes inherit the member variables of Animal to reuse its properties.
- Keep a unified interface for all derived classes to override it to achieve polymorphism.
Hence, we can defined Animal as an abstract class:
class Animal {
public:
Animal(string name) : _name(name) {}
virtual void bark() = 0;
protected:
string _name;
};
Here we declared a virtual function bark(), but assign its address to 0. In this case, bark() is a pure virtual function. A class with pure virtual functions is called an abstract class. An abstract class can not be instantiated, but can have pointers and references:
int main() {
Animal a; // No
Animal *pa; // Yes
return 0;
}
Let’s look at another example. Here we defined an abstract class Car, and has a method which returns remaining miles under the current fuel level.
class Car {
public:
Car(string name, double oil) : _name(name), _oil(oil) {}
double getLeftMiles(double oil) {
return oil * getMilesPerGallon();
}
protected:
string _name;
double _oil;
virtual double getMilesPerGallon() = 0;
};
Then we defined three cars of different brands, with different mileages per gallon.
class Benz : public Car {
public:
Benz(string name, double oil) : Car(name, oil) {}
double getMilesPerGallon() {return 20.0;}
};
class Audi : public Car {
public:
Audi(string name, double oil) : Car(name, oil) {}
double getMilesPerGallon() {return 18.0;}
};
class BMW : public Car {
public:
BMW(string name, double oil) : Car(name, oil) {}
double getMilesPerGallon() {return 19.0;}
};
Moreover, we provide an interface to get the remaining miles of the car, and passes in different objects:
double showCarLeftMiles(Car &car) {
cout << car.getName() << " " << car.getLeftMiles() << endl;
}
int main() {
Benz a("Benz", 20.0);
Audi b("Audi", 20.0);
BMW c("BWM", 20.0);
showCarLeftMiles(a); // Benz 400
showCarLeftMiles(b); // Audi 360
showCarLeftMiles(c); // BMW 380
return 0;
}
Now in showCarLeftMiles(), the parameter is a Car object instead of a pointer, isn’t it a static binding? How can we achieve polymorphism? Of course it is a static binding here, but notice that in Car::getLeftMiles(), the virtual function getMilesPerGallon() is called with this->getMilesPerGallon(). Since *this is a Car pointer, it is still a dynamic binding, so the corresponding method of different objects is called.