Advanced Oop
By the end of this lesson you'll be able to share data across a class with static members, grant trusted access with friend , tame multiple inheritance and the diamond problem, query an object's real type at runtime with dynamic_cast , lock hierarchies down with final / override , and correctly manage resources with the rule of three/five.
Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
Think of a class as a company . Each employee (object) has their own desk and tasks (instance members). But the company has one shared noticeboard everyone reads — that's a static member: it belongs to the company, not to any single person. A friend is the external auditor you deliberately give a key to the private filing cabinet. Multiple inheritance is an employee reporting to two managers — and the diamond problem is when both managers ultimately report to the same director: do you get one director or two? virtual inheritance guarantees there's only ever one director.
1. Static Members & Methods
A static member belongs to the class itself, not to any individual object — there's exactly one shared copy no matter how many objects you create. A static method can be called on the class ( BankAccount::howMany() ) without an object, but it can only touch static data because it has no this . One catch beginners always hit: a static data member must be defined once outside the class. Read this worked example, run it, then you'll write your own.
Your turn. The program below is almost complete — fill in the three blanks marked ___ using the // 👉 hints, then run it.
2. friend Functions & Classes
Normally private members are off-limits to the outside world. A friend declaration is you deliberately granting one specific function — or an entire class — permission to reach inside. It's a controlled exception, not a leak: the class still decides exactly who gets in. The classic use is letting a printing helper or a tightly-paired class read your internals without exposing them to everyone.
3. Multiple Inheritance & the Diamond Problem
C++ lets a class inherit from more than one base at once. That power creates the diamond problem : when two bases ( Printer and Scanner ) both inherit the same ancestor ( Device ), a class inheriting both would get two copies of Device — so any member like serial becomes ambiguous. The fix is virtual inheritance : write class Printer : virtual public Device on both sides, and the compiler keeps a single shared Device . With a virtual base, the most-derived class is responsible for constructing it.
4. dynamic_cast , RTTI, final & override
RTTI (Run-Time Type Information) lets your program ask, while it's running, what an object's real type is. The tool is dynamic_cast : cast a base pointer down to a derived pointer and, if the object really is that type, you get a usable pointer — otherwise you get nullptr (no crash), which you test with an if . It only works on polymorphic types (those with at least one virtual function), because the check reads the vtable. Two safety keywords complete the picture: override makes the compiler verify you actually overrode a base method (catching signature typos), and final forbids any further overriding or subclassing.
Now you try. inspect receives a Shape* and should call radius() only when the shape is really a Circle . Fill in the two blanks:
5. The Rule of Three / Five
When a class owns a resource (heap memory, a file handle, a socket), the compiler's automatic copy behaviour is wrong — it copies the pointer , so two objects end up sharing and then double-freeing the same memory. The rule of three says: if you write any one of the destructor, copy constructor, or copy assignment, you almost certainly need all three. Modern C++ adds two more — the move constructor and move assignment — which cheaply steal a resource instead of copying it; together that's the rule of five . (The happiest path is the rule of zero : use std::string / std::vector / std::unique_ptr so you write none of them.)
Common Errors (and the fix)
- "request for member 'x' is ambiguous" (diamond ambiguity): a class inherited the same base twice and now has two copies of x . Make the inheritance virtual public Base on both intermediate classes so only one copy exists.
- Forgetting to construct the virtual base: with virtual inheritance the most-derived class must call the base constructor itself — e.g. MFP(sn) : Device(sn), Printer(sn), Scanner(sn) . Leave out Device(sn) and you get a "no matching constructor" error (or the wrong default).
- "cannot dynamic_cast … (target is not pointer or reference to complete type)" / it just won't compile: you used dynamic_cast on a non-polymorphic type. Add at least one virtual function (a virtual ~Base() = default; is enough) so RTTI exists.
- Double free / corrupted heap (rule-of-three violation): you wrote a destructor that delete s a pointer but let the compiler generate the copy constructor — two objects now own the same pointer and both free it. Provide a deep-copying copy constructor and copy assignment (or delete them).
- "marked 'override' but does not override": your signature doesn't match the base (often a missing const ). Fix the signature — this error is exactly why override exists.
📋 Quick Reference
Concept
Syntax
Result
Static data member
static int count;
One copy shared by all objects
Define static member
int C::count = 0;
Required once, outside the class
Friend function
friend void f(const C&);
f may read private members
Virtual inheritance
class D : virtual public B
One shared copy of B (diamond fix)
Safe downcast
dynamic_cast<Dog*>(a)
Dog* if it is one, else nullptr
Checked override
void f() const override;
Compiler verifies it overrides
Seal a class/method
class Cat final
No further subclassing/overriding
Rule of five
~C(); C(const C&); C& operator=(…); C(C&&); …
Manage copy + move + destroy
Pro Tips
- 💡 Prefer the rule of zero: reach for std::vector , std::string , and std::unique_ptr so you don't have to write any of the five special functions yourself.
- 💡 Always add override : it costs nothing and turns a silent "new method" bug into a compile error.
- 💡 Prefer composition over multiple inheritance: "has-a" is usually clearer than juggling two base classes and a diamond.
- 💡 Reach for dynamic_cast sparingly: if you're testing the type a lot, a virtual method on the base is often the cleaner design.
Frequently Asked Questions
Mini-Challenge: Counted Widgets
No blanks this time — just a brief and an outline. Combine a static counter with a friend printer, build it, run it, and check your output against the example in the comments.
🎉 Lesson Complete
- ✅ static members and methods give a class one shared copy of data (define static data once outside the class)
- ✅ friend grants one function or class controlled access to private members
- ✅ Multiple inheritance can hit the diamond problem; virtual public Base keeps a single shared base
- ✅ dynamic_cast + RTTI safely test an object's real type (polymorphic types only; nullptr on failure)
- ✅ override catches signature typos; final seals classes and methods
- ✅ The rule of three/five keeps resource-owning classes from double-freeing — or use the rule of zero
- ✅ Next lesson: Templates Deep Dive — variadic templates, SFINAE, and compile-time metaprogramming
Practice quiz
A static data member of a class is best described as:
- One copy per object
- Always private
- One copy shared by the whole class
- A function with no body
Answer: One copy shared by the whole class. There is exactly one shared static member no matter how many objects exist; it must also be defined once outside the class.
Why can a static member function only access static members?
- It has no 'this' pointer, so there is no object to reach instance members through
- It runs at compile time
- Static members are always public
- It is implicitly const
Answer: It has no 'this' pointer, so there is no object to reach instance members through. A static method is called on the class itself (BankAccount::howMany()) with no object, so it has no 'this'.
What does a 'friend' declaration grant?
- Inheritance from another class
- Automatic copy construction
- Thread safety
- A specific function or class access to this class's private members
Answer: A specific function or class access to this class's private members. friend is a controlled exception that lets one named function or class reach the private members.
In the diamond problem, what does marking the inheritance 'virtual' achieve?
- It makes methods virtual
- It keeps a single shared copy of the common base instead of two
- It deletes the base class
- It speeds up dispatch
Answer: It keeps a single shared copy of the common base instead of two. virtual inheritance (class Printer : virtual public Device) keeps one shared Device, removing the ambiguity.
With a virtual base class, which class is responsible for constructing that base?
- The most-derived class
- The first base listed
- Each intermediate class
- The compiler, automatically with defaults only
Answer: The most-derived class. The most-derived class constructs the virtual base directly, e.g. MFP(sn) : Device(sn), Printer(sn), Scanner(sn).
dynamic_cast<Dog*>(animalPtr) returns what when the object is NOT a Dog?
- A garbage pointer
- It throws an exception
- nullptr
- It crashes
Answer: nullptr. On a pointer, a failed dynamic_cast yields nullptr, which you test with an if — no crash.
dynamic_cast only works on which kind of types?
- Any type
- Polymorphic types (with at least one virtual function)
- Only POD structs
- Only template types
Answer: Polymorphic types (with at least one virtual function). RTTI reads the vtable, so the type needs at least one virtual function (a virtual destructor is enough).
What does adding 'override' to a member function do?
- Makes it virtual for the first time
- Prevents inheritance
- Makes it static
- Tells the compiler to verify it actually overrides a base method, catching signature typos
Answer: Tells the compiler to verify it actually overrides a base method, catching signature typos. If your signature doesn't match a base virtual (e.g. a missing const), override turns a silent bug into a compile error.
The 'rule of three' says that if you write one of these, you likely need all three:
- Constructor, getter, setter
- Destructor, copy constructor, copy assignment
- Move constructor, move assignment, swap
- begin, end, size
Answer: Destructor, copy constructor, copy assignment. Managing a resource means defining the destructor, copy constructor, and copy assignment together (the rule of five adds the two move operations).
After Buffer c = std::move(a); in the rule-of-five example, what is the state of 'a'?
- Unchanged, still holding its data
- A deep copy of c
- Moved-from: left empty but valid
- Destroyed and unusable
Answer: Moved-from: left empty but valid. The move constructor steals a's pointer and sets a.data to nullptr, leaving 'a' empty but in a valid state.
Continue this course
- Previous: Modern C++ Memory Model
- Next: Templates Deep Dive