Classes and OOP
7 pages · ~5 min✓ Reviewed
Builds on Basics, Pointers and References. Next up: Memory, RAII and Smart Pointers.
Classes, Structs and Construction
1. class vs struct
| Default | class | struct |
|---|---|---|
| Member access | private | public |
| Inheritance | private | public |
- That is the only difference
- Constructors: no return type, run once per object
- Compiler adds a default constructor only if you declare no constructor
2. Initializer list
cpp
struct Foo {
int x, y;
Foo(int a) : x(a), y(a * 2) {}
Foo() : Foo(0) {} // delegating
};list runs before the body
- Required for
constmembers, reference members, members with no default constructor - Members init in declaration order, not list order
- Reordering the list changes nothing;
-Wreorderwarns when one member uses another
3. explicit
- Mark one-argument constructors explicit
- Blocks implicit conversion:
Foo f = 5;fails to compile - Delegating constructor
Foo(): Foo(0) {}reuses one constructor
Init order
Declaration order decides, never the initializer list.
Destructors and the Rule of Three, Five and Zero
1. Destructor and Rule of Three
~Foo()runs at scope exit ordelete- Members destroyed in reverse declaration order, then bases
- Custom destructor, copy ctor or copy assign: you need all three
- Shallow copy of a raw pointer causes double delete
2. Five and Zero
| Rule | You write |
|---|---|
| Three | destructor, copy ctor, copy assign |
| Five | + Foo(Foo&&) noexcept, move assign |
| Zero | nothing: hold unique_ptr, vector, string |
noexceptmoves letstd::vectormove on growth, not copy- Rule of zero: compiler-generated ones are correct
3. Assignment and defaults
- Copy assign must survive self-assignment (
a = a) - Copy-and-swap gives that plus exception safety
Inheritance and Virtual Functions
1. Is-a and Order
- Public inheritance models is-a
Derived(): Base(args) {}runs the base ctor first- Destruction runs in reverse order
2. Binding and the vtable
| No virtual | virtual | |
|---|---|---|
| Binds | compile time | run time |
| Decided by | pointer's static type | object's dynamic type |
- One vtable per class with virtuals: function pointers
- Each object has a hidden vptr: one pointer of size
- Each virtual call costs one indirect call
overrideturns a signature mismatch into a compile errorfinalblocks further overriding or inheriting
cpp
struct B { virtual void f() const; virtual ~B() = default; };
struct D : B { void f() const override; };
struct E final : D {};override on every override
3. Mistakes
Abstract Classes and Virtual Destructors
1. Pure Virtual = Abstract
virtual void draw() const = 0;makes the class abstract- Cannot be instantiated; use it only through pointers or references
- A derived class stays abstract until it overrides every pure virtual
- A pure virtual may still have a body, called as
Base::draw()
cpp
struct Shape {
virtual ~Shape() = default;
virtual double area() const = 0;
};
std::vector<std::unique_ptr<Shape>> shapes;
for (auto& s : shapes) s->area();interface held by unique_ptr: runtime polymorphism
2. Virtual Destructors
- A base with virtual functions needs
virtual ~Base() - Non-virtual dtor +
deleteviaBase*= undefined behavior, skips ~Derived - A pure virtual destructor must be defined
cpp
struct Base { virtual ~Base() = 0; };
Base::~Base() {} // body is requiredInterface recipe
virtual ~X() = default; plus pure virtuals, and own objects with unique_ptr<X>.
3. Interview Point
- Why a virtual destructor?
deletethroughBase*then runs ~Derived too
Operator Overloading
1. The Rules
- An operator is a function named operator+, etc.
- No new operators; precedence is fixed
- Not overloadable:
::..*?:sizeof
2. Member or Free?
| Form | Operators |
|---|---|
| Member (required) | = [] () -> |
| Free (symmetric) | a + b, a == b, << |
- Free lets implicit conversion work on the left operand
3. Patterns
cpp
Foo& Foo::operator+=(const Foo& b) { x += b.x; return *this; } Foo operator+(Foo a, const Foo& b) { return a += b; } std::ostream& operator<<(std::ostream& os, const Foo& f) { return os << f.x; } auto operator<=>(const Foo&) const = default; Foo& operator++(); // prefix Foo operator++(int); // postfix
- Write
+=first, build+on it <<is free (oftenfriend), returns the stream
C++ OOP Cheat Sheet
1. Special Members and Order
| Member | Signature |
|---|---|
| Default ctor | Foo() |
| Copy ctor | Foo(const Foo&) |
| Copy assign | Foo& operator=(const Foo&) |
| Move ctor | Foo(Foo&&) noexcept |
| Move assign | Foo& operator=(Foo&&) noexcept |
| Destructor | ~Foo() |
- Prefer rule of zero; else define all five
- Destroy in exact reverse of construction
- Basesfirst
- Membersdeclaration order
- Ctor bodylast
2. Polymorphic Base Checklist
virtual ~Base()overrideon every override- No slicing: pass by reference or pointer
- No virtual calls in ctor/dtor
Check yourself
What does b hold after P p;?
- Cause: declaration order is b, a, not list order
- Result: b reads uninitialised a, which is undefined behavior
-Wreorderflags this
struct P { int b, a;
P(): a(1), b(a + 1) {} };Base has a virtual function but a non-virtual destructor. What goes wrong here?
- Deleting through
Base*is undefined behavior;~Derivedis skipped - Fix:
virtual ~Base()
Base* p = new Derived; delete p;
What does D d; print?
- It prints B
- Inside
B()the object is still aB, so the call binds toB::f
struct B { B() { f(); }
virtual void f() { std::cout << "B"; } };
struct D : B {
void f() override { std::cout << "D"; } };