oops-04-virtual-abstract-diamond.md

Virtual Functions, Abstract Classes, Static Usage and Diamond Problem

Priority: HIGH — Diamond problem and virtual functions are classic deep-dive follow-ups Spoken answer target: under 45 seconds each


1. Virtual Functions — Full Theory

Definition:

A function declared with virtual in a base class that enables dynamic dispatch: the correct derived-class version is called through a base pointer/reference at runtime.

Key Point — vtable mechanism:

  • Compiler creates a vtable (virtual table) for every class with virtual functions
  • vtable is an array of function pointers — one slot per virtual function
  • Each object gets a hidden vptr (virtual pointer) pointing to its class's vtable
  • Virtual call: read vptr from object → index into vtable → call function pointer
  • This lookup happens at runtime — that's why it's "dynamic dispatch"
class Animal { public: virtual void speak() const { cout << "..."; } virtual void eat() const { cout << "Eating"; } virtual ~Animal() {} }; class Dog : public Animal { public: void speak() const override { cout << "Woof!"; } // eat() not overridden — uses Animal's version }; // Memory layout of a Dog object: // [vptr | Animal data | Dog data] // | // v // Dog's vtable: // [0] -> Dog::speak() (overridden) // [1] -> Animal::eat() (inherited — same as Animal's vtable slot) // [2] -> ~Dog() (destructor) Animal* ptr = new Dog(); ptr->speak(); // Runtime: read ptr->vptr, vtable[0] = Dog::speak, CALL IT // Output: "Woof!" (correct!)

Non-virtual vs Virtual call:

Non-virtual: compiler generates a direct function call (address known at compile time) Virtual: compiler generates: load vptr, load function pointer, indirect call ~2 extra memory accesses, ~1-5ns overhead — usually negligible

Likely follow-up:

"Can a constructor be virtual?" No. During construction, the vtable pointer (vptr) is being set up — it doesn't exist yet for the derived class. Virtual functions rely on vptr, which is not set until the constructor of the current class runs. Use the Factory Method pattern for "virtual construction."


2. Pure Virtual Functions

Definition:

A virtual function declared with = 0 — no implementation in the base class. Forces every concrete derived class to provide its own implementation.

Syntax:

virtual double area() const = 0; // Pure virtual

Rules:

  1. A class with at least one pure virtual function is abstract
  2. Abstract classes CANNOT be instantiated directly
  3. Derived class must override ALL pure virtual functions to become concrete
  4. You CAN have a pointer/reference to an abstract class (enables polymorphism)
  5. Abstract classes CAN have: constructors, non-pure virtual functions, data members, static members
class Shape { protected: string color; public: Shape(string c) : color(c) {} virtual double area() const = 0; // Pure virtual virtual double perimeter() const = 0; // Pure virtual // Regular virtual with default — can be overridden virtual string getInfo() const { return "A " + color + " shape"; } virtual ~Shape() = default; }; // Shape s("red"); // ERROR: abstract class! class Circle : public Shape { double r; public: Circle(string color, double r) : Shape(color), r(r) {} double area() const override { return 3.14159 * r * r; } double perimeter() const override { return 2 * 3.14159 * r; } // getInfo() — not overridden, uses Shape's default }; // This still compiles and works: Shape* s = new Circle("red", 5); // Pointer to abstract class — OK cout << s->area(); // 78.5 — virtual dispatch works delete s;

3. Abstract Class vs Static Usage

Abstract class — use when:

  • You have a family of related classes that share some behavior but must specialize specific functions
  • You want to define a template/contract for derived classes to follow
  • You NEED polymorphism (different objects in the same collection)

Static members/functions — use when:

  • You need data/behavior that belongs to the CLASS, not any specific object
  • Counter of instances, factory method, utility functions that don't need object state
  • Static function has NO this pointer — cannot access non-static members
// ABSTRACT CLASS — defines what derived shapes MUST implement class Shape { public: virtual double area() const = 0; virtual ~Shape() = default; }; // STATIC USAGE — belongs to the class, not an object class ShapeFactory { static int shapeCount; public: static Shape* create(const string& type, double size); static int getCount() { return shapeCount; } // ShapeFactory sf; sf.getCount(); -- works but odd // ShapeFactory::getCount(); -- preferred (no object needed) }; int ShapeFactory::shapeCount = 0;

Comparison:

Abstract ClassStatic Members/Functions
PurposeDefine a contract/templateClass-level shared data or utilities
InstantiationCannot create objectsCan (but often not needed for utility classes)
PolymorphismYes — base pointers/referencesNo — static binding always
Object neededNo — but pointer worksNo (static), Yes (non-static)
Access to thisYes (in non-static methods)No (static functions have no this)

Likely follow-up:

"Can an abstract class have a constructor?" Yes! Abstract class constructors are called by derived class constructors. They initialize the base portion of the object. They just cannot be called directly to create a standalone object.

Common mistake: Forgetting that static functions cannot access non-static members. If you call a static function, there is no object, so there is no this pointer and no instance data.


4. Diamond Problem

Definition:

The diamond problem occurs in multiple inheritance when class D inherits from B and C, and both B and C inherit from A. D ends up with TWO copies of A's data, causing ambiguity and wasted memory.

Visual:

[A] / \ [B] [C] \ / [D] D has: D::B::A (copy 1 of A) D::C::A (copy 2 of A) d.eat() -- ERROR: ambiguous! Which A::eat()? d.weight -- ERROR: ambiguous! Which A::weight?
class A { public: int weight; void eat() { cout << "A eating, weight=" << weight; } }; class B : public A { public: void breathe() {} }; class C : public A { public: void layEggs() {} }; class D : public B, public C { public: void fly() {} }; int main() { D d; // d.eat(); // ERROR! Ambiguous — B::A::eat() or C::A::eat()? // d.weight = 5; // ERROR! Ambiguous — which copy? d.B::eat(); // Explicit path — ugly workaround d.C::eat(); // Still two separate A sub-objects! d.B::weight = 100; d.C::weight = 200; // Different values — two separate copies! }

5. Virtual Inheritance — The Solution

Definition:

Virtual inheritance ensures only ONE shared copy of the base class exists regardless of how many paths lead to it. The most-derived class is responsible for initializing the virtual base.

class A { public: int weight; A(int w = 0) : weight(w) {} void eat() { cout << "A eating, weight=" << weight; } }; // VIRTUAL inheritance — only ONE copy of A will exist class B : virtual public A { public: B() { cout << "B"; } }; class C : virtual public A { public: C() { cout << "C"; } }; class D : public B, public C { public: // Most-derived class MUST initialize the virtual base directly D() : A(50) { cout << "D"; } // ^^^^^ D initializes A, NOT B or C (B's and C's A-init is ignored) }; int main() { D d; d.eat(); // No ambiguity! Only ONE A d.weight = 75; // Only ONE weight field — clear } // Memory layout WITHOUT virtual: // [B::A::weight | B data | C::A::weight | C data | D data] // ^two copies of A // Memory layout WITH virtual: // [vbptr | B data | vbptr | C data | D data | A::weight] // ^ONE shared A

Construction order with virtual inheritance:

1. Virtual base (A) constructed FIRST — by the most-derived class (D) 2. B constructed 3. C constructed 4. D constructed B and C's A-initialization in their initializer lists is IGNORED when D exists

Tradeoffs of virtual inheritance:

  • Pros: solves ambiguity, saves memory
  • Cons: adds vbptr (virtual base pointer) overhead, slightly slower member access, constructor rules are more complex

Likely follow-up:

"Can Java have the diamond problem?" No — Java does not allow inheriting from multiple classes. You can implement multiple interfaces, but interfaces (before Java 8) had no implementation, so no conflict. From Java 8, if two interfaces have the same default method, the implementing class must explicitly override it.

Common mistake: Not making ALL intermediate classes use virtual inheritance. If B uses virtual public A but C uses just public A, D still gets two copies (one through virtual B, one direct through C).


6. override and final — Safety Nets

class Base { public: virtual void foo(int x) const {} virtual void bar() {} virtual ~Base() = default; }; class Derived : public Base { public: // WITHOUT override: if signature doesn't match, creates a NEW function (silent bug!) // void foo(int x) {} // Missing const! Creates a DIFFERENT function! // WITH override: compile error if signature doesn't match base virtual void foo(int x) const override {} // Correct void bar() override {} // Correct // void baz() override {} // ERROR! No virtual baz() in Base to override }; // final on a class: CANNOT inherit from it class MySingleton final { // No class can extend this }; // final on a function: CANNOT override in further derived classes class Mammal : public Animal { public: void breathe() override final {} // Locked — Dog cannot override this };

Rule: ALWAYS use override when overriding. It catches: typos in function name, wrong const, wrong parameter type, base function not virtual.


7. Object Slicing — Common Bug

Definition:

When a derived object is copied (by value) into a base object, the derived-specific data is "sliced off." Polymorphism is lost — virtual dispatch no longer works correctly.

class Animal { public: string name; virtual void speak() { cout << "..."; } }; class Dog : public Animal { public: string breed; // Sliced off! void speak() override { cout << "Woof!"; } }; int main() { Dog d; d.name = "Buddy"; d.breed = "Lab"; Animal a = d; // SLICING! breed is lost, vtable reverts to Animal's a.speak(); // "..." -- Animal::speak, NOT Dog::speak! // a.breed; // ERROR -- doesn't exist anymore // CORRECT: use pointer or reference Animal* ptr = &d; ptr->speak(); // "Woof!" -- correct virtual dispatch! Animal& ref = d; ref.speak(); // "Woof!" -- also correct! }

Rule: Never pass polymorphic objects by value. Always use pointers or references.


Quick Reference Table

ConceptKey syntax / ruleCommon trap
Virtual functionvirtual void foo()Missing virtual = no dynamic dispatch
Pure virtualvirtual void foo() = 0Forgetting one = derived still abstract
Abstract classHas at least one = 0Cannot instantiate, CAN have constructors
Static functionstatic void foo()No this pointer, cannot access instance data
Diamond problemD inherits B and C which both inherit ATwo copies of A — ambiguity
Virtual inheritanceclass B : virtual public AMost-derived class must initialize virtual base
overridevoid foo() overrideMissing — silent hiding bug instead of override
finalclass X final or void foo() finalPrevents further inheritance/override
Object slicingAnimal a = dog;Derived data lost, vtable reverts to Base