The four pillars of OOP
Object oriented programming
Encapulation
- A class is built to encapsulate behaviour & state
- It exposes members through a well thought-out design.
- To enforce encapulation we make use of access specifiers
- private: Only this class can access private members.
- protected: Only this class and derived classes can access these members.
- public: Members that are public are accessable outside the class.
Abstraction
- A class should hide implementation while exposing only members that are required
- To enforce this, we use abstract classes, interfaces.
Encapulation & Abstraction differences
These two concepts seem a bit too related so here are some differences
| Encapulation | Abstraction |
|---|---|
| Hides data | Hides implementation |
| Focuses on security | Focuses on simplicity |
| Uses access modifiers | Uses abstract classes/interfaces |
Inheritance
- Common behaviour can be abstracted & encapsulate into a base class. Other classes can inherite properties & behaviour from a base class.
- Inheritance promotes code reusability
Polymorphism
- Polymorphism means many forms.
- There are two types of polymorphism. Compile time & Runtime polymorphism.
- Compile-time Polymorphism: Achieved via Method Overloading, Operator Overloading, Curiously Recurring Template Pattern (CRTP), Templates.
- Runtime Polymorphism: Achieved via Function Overriding using the virtual keyword. This allows a base class pointer to call the specific implementation of a derived class.
Compile-type polymorphism (CRTP)
Curiously Recurring Template Pattern (CRTP) is a C++ technique where a class inherits from a template instantiated with itself. The derived class passes itself as the template parameter.
It is often used to emulate interfaces/mixins without virtual overhead.
template<typename Derived>
class Animal {
public:
void speak() {
static_cast<Derived*>(this)->speakImplementation();
}
};
class Dog : public Animal<Dog> {
public:
void speakImplementation() {
std::cout << "Bark" << std::endl;
}
};
Note: Important Limitation CRTP is not substitutable polymorphism. You cannot do:
Animal* a = new Dog(); // invalid
Runtime polymorphism
class Animal {
public:
virtual ~Animal() {}
virtual void speak() {
std::cout << "..." << std::endl;
}
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Bark" << std::endl;
}
};
Animal* animal = &baseAnimal; // speak() should output "..."
Dog dog; // speak() should output "Bark"
animal = &dog;
animal->speak(); // "Bark"
When you call animal->speak();, the program looks into the vtable (virtual table) for the Dog class and executes Dog::speak() Even though the typeof(animal) is Animal. This is runtime polymorphism.
CRTP vs Virtual Functions
| CRTP | Virtual Functions |
|---|---|
| Compile-time dispatch | Runtime dispatch |
| No vtable | Uses vtable |
| No RTTI required | RTTI often associated |
| Can inline easily | Harder to inline |
| Faster | More flexible |
| Types fixed at compile time | Types chosen at runtime |