overriding vs. overloading

Overloading (static polymorphism)Overriding (dynamic polymorphism)
Resolvedat compile time, by argument typesat runtime, by the object's actual type
Requires inheritancenoyes, plus a virtual function
Scopesame scope — multiple functions with the same name coexistdifferent scopes — a derived class's version replaces the base's for dynamic dispatch
Signaturemust differ (parameter count or types)must match exactly (same return type and parameters)
A function marked override doesn't do anything at runtime — it's a compile-time check that the function actually overrides a virtual function with a matching signature in the base class. Without it, a typo in the signature (wrong parameter type, a missing const) silently creates an unrelated overload instead of an override, and the bug only shows up as "why isn't my override being called" at runtime.

the vtable, and what a virtual function actually costs

Every class that declares or inherits a virtual function gets a vtable — one array of function pointers per class (not per object), one entry per virtual function. Every instance of that class carries a hidden vptr pointing at its class's vtable, which is why adding even one virtual function grows every instance by one pointer's worth of bytes (8 on a 64-bit platform) regardless of how many virtual functions the class ends up with.

class NoVirtual   { int *ptr; float f; bool b; void print(){} };            // 16 bytes — no vptr
class WithVirtual { int *ptr; float f; bool b; virtual void print(){} };    // 24 bytes — +8 for the vptr
            
Calling a virtual function costs about the same as a regular call — follow the object's vptr to its vtable, look up the function pointer at a fixed offset, call it. Four real costs, worth knowing precisely rather than treating virtual functions as vaguely "expensive":
CostWhat it is
1. Vtable storageone array per class with virtual functions — negligible unless you have hundreds of classes with many virtual functions each
2. Vptr per object+8 bytes per instance, whether or not that instance ever calls a virtual function
3. No inliningthe real cost — a call through a base pointer/reference can't be resolved until runtime, so the compiler can't inline it even when it safely could for a non-virtual call
4. RTTIone type_info object per class plus one vtable slot to point at it — doesn't grow individual objects, only the vtable
None of this is a reason to avoid virtual functions — hand-rolling the equivalent dispatch logic yourself (a switch on a type tag, say) has to pay for the same indirection and is usually slower and much more error-prone to maintain. Verify the layout for yourself on a real class: TopNotchNote/cpp/inheritance_vtable_diamond.cpp

the virtual destructor rule

If a base class destructor isn't virtual, deleting a derived object through a base pointer only runs the base class's destructor — the derived part is never cleaned up. Any class meant to be used polymorphically (deleted or destroyed through a base pointer) needs a virtual destructor, full stop.

class Base    { public: ~Base() { std::cout << "~Base\n"; } };            // NOT virtual
class Derived : public Base { public: ~Derived() { std::cout << "~Derived\n"; } };

Base *p = new Derived();
delete p;   // prints only "~Base" — ~Derived never runs. Any Derived-owned resources leak.
            

class Base    { public: virtual ~Base() { std::cout << "~Base\n"; } };     // virtual fixes it
class Derived : public Base { public: ~Derived() { std::cout << "~Derived\n"; } };

Base *p = new Derived();
delete p;   // prints "~Derived" then "~Base" — both run, in the correct order
            

the diamond problem and virtual inheritance

When two base classes both inherit from a common ancestor, and a class inherits from both of them, that ancestor's data ends up duplicated — the ancestor's constructor runs twice, and the derived class holds two independent copies of every member the ancestor declared.

//         PoweredDevice
//          /         \
//    Scanner        Printer
//          \         /
//        AllInOnePrinter

class PoweredDevice { public: PoweredDevice() { std::cout << "PoweredDevice ctor\n"; } };
class Scanner  : public PoweredDevice {};
class Printer  : public PoweredDevice {};
class AllInOnePrinter : public Scanner, public Printer {};   // now has TWO PoweredDevice subobjects

AllInOnePrinter printer;   // prints "PoweredDevice ctor" twice — printer.Scanner::??? and printer.Printer::??? refer to different PoweredDevices
            
Declaring the shared base virtual in both intermediate classes collapses the two copies back into one, and shifts responsibility for constructing that shared base to the most-derived class — even a parameterized PoweredDevice constructor called by Scanner or Printer is ignored unless AllInOnePrinter calls it directly.

class PoweredDevice { public: PoweredDevice() { std::cout << "PoweredDevice ctor\n"; } };
class Scanner  : virtual public PoweredDevice {};
class Printer  : virtual public PoweredDevice {};
class AllInOnePrinter : public Scanner, public Printer {};

AllInOnePrinter printer;   // prints "PoweredDevice ctor" once — a single shared subobject
            

virtual constructor / factory pattern

A constructor itself can never be virtual — the object doesn't exist yet, so there's no vptr to dispatch through. The "virtual constructor" idiom achieves the same practical goal (pick the concrete type to create based on runtime information) through a static factory function instead, decoupling the caller from knowing which concrete class it's getting:

class Shape {
public:
  static Shape *create(int id);          // the "virtual constructor" — really a factory function
  virtual ~Shape() = default;             // still needs a real virtual destructor
  virtual double area() const = 0;
};

class Circle : public Shape { public: double area() const override { return 3.14159; } };
class Square : public Shape { public: double area() const override { return 4.0; } };

Shape *Shape::create(int id) {
  if (id == 0) return new Circle();
  if (id == 1) return new Square();
  return nullptr;
}

Shape *s = Shape::create(0);   // caller never names Circle or Square directly
            
This is the same idea covered as a named pattern in Design Patterns (Factory Method) — it's included here because it's the direct answer to "why can't I make a constructor virtual."

abstract classes and pure virtual functions


class Shape {
public:
  virtual double area() const = 0;   // pure virtual — no body, makes Shape abstract
  virtual ~Shape() = default;
};

// Shape s;             // error: cannot instantiate an abstract class
Shape *p = new Circle();  // fine — a concrete derived class can be instantiated
            
A class with at least one pure virtual function is abstract: it defines an interface without any promise of implementing it, and can only be used through pointers/references to a concrete derived class. Shape s = Circle(); specifically fails to compile because Shape is abstract — you can't instantiate it at all, by value or otherwise. But the deeper reason polymorphism only works through a pointer or reference holds even for non-abstract base classes: Base b = Derived(); would compile fine, but it slices the object down to just its Base part, silently discarding the derived-class data and any overridden behavior — no virtual dispatch happens on a by-value object.

where to go from here

Casting — static_cast/dynamic_cast, and up/down-casting between base and derived.
Design Patterns — the Factory Method, Strategy, and other patterns that lean on virtual dispatch.
OOP Relationships — inheritance ("is-a") vs. composition and aggregation ("has-a"/"uses-a").

reference

cppreference — virtual function specifier
cppreference — derived classes