# File 2: Core Object-Oriented C++

## Learning Goals

By the end of this file, you should be able to:

- explain what a C++ class actually represents in memory and in code
- reason about constructors, destructors, and object lifetime without hand-waving
- use encapsulation and abstraction to protect invariants
- distinguish inheritance from polymorphism and understand when each is appropriate
- recognize common object-oriented mistakes that cause subtle bugs in production C++

This file builds directly on the foundations from File 1. C++ object-oriented features are not separate from the memory model. A class is still a concrete object layout plus functions that operate on it.

## Why Object-Oriented Features Exist in C++

### Intuition

As programs grow, raw functions and primitive types stop being enough. You need a way to keep data and the rules for using that data together.

That is the heart of classes in C++:

- package state with behavior
- enforce invariants at the boundary
- model domain concepts clearly
- make ownership and lifetime more explicit

In real systems, object-oriented design is less about textbook hierarchy diagrams and more about making illegal states harder to represent.

## Classes and Objects

### Intuition

A class is a user-defined type. It describes:

- what data an object holds
- what operations are allowed on that data
- what rules govern creation, use, and destruction

An object is an instance of that type occupying real storage at runtime.

### Example

```cpp
class BankAccount {
public:
    explicit BankAccount(double starting_balance)
        : balance_(starting_balance) {}

    void deposit(double amount) {
        balance_ += amount;
    }

    bool withdraw(double amount) {
        if (amount > balance_) {
            return false;
        }
        balance_ -= amount;
        return true;
    }

    double balance() const {
        return balance_;
    }

private:
    double balance_;
};
```

### What Happens Internally

At runtime, an object usually contains only its data members. Member functions are not copied into every object. They are compiled as ordinary functions that receive an implicit object parameter, usually called `this`.

Conceptually, this:

```cpp
account.deposit(50.0);
```

behaves like:

```cpp
deposit(&account, 50.0);
```

That is not exact source-level syntax, but it is the right mental model.

### Object Layout Mental Model

```mermaid
flowchart LR
    A[BankAccount object] --> B[balance_ : double]
    C[Member functions] --> D[operate using this pointer]
```

### Practical Usage

Classes are useful when data has invariants:

- account balances should not go negative unless explicitly allowed
- sockets should not be used after closure
- file handles must be released exactly once
- caches should hide eviction details behind a stable API

### Common Pitfalls

- making everything public and losing invariant protection
- creating “data bag” classes that do not meaningfully model behavior
- assuming classes are automatically heap-allocated; in C++, class objects can live on the stack, in static storage, or on the heap

## Access Control, Encapsulation, and Abstraction

### Intuition

Encapsulation is about protecting internal state from invalid use. Abstraction is about exposing the right conceptual interface while hiding irrelevant details.

These are related but not identical.

- encapsulation protects data and invariants
- abstraction reduces cognitive load for callers

### How It Works

Access specifiers such as `public`, `private`, and `protected` control what code may access certain members.

In the `BankAccount` example, `balance_` is private. That forces all mutations to go through functions that can enforce rules.

### Why This Matters in Real Systems

Without encapsulation, every caller can put an object into a bad state. In a large codebase, that turns local correctness into a global burden.

Good class design moves validation and lifecycle rules into one place so they are not reimplemented badly in ten different subsystems.

### Example: Protecting an Invariant

```cpp
class Percentage {
public:
    explicit Percentage(int value) {
        if (value < 0 || value > 100) {
            throw std::out_of_range("percentage must be between 0 and 100");
        }
        value_ = value;
    }

    int value() const {
        return value_;
    }

private:
    int value_;
};
```

If `value_` were public, every call site would need to remember the rule. That does not scale.

### Common Misconception

“Encapsulation means getters and setters for everything.”

No. Blind getters and setters often expose implementation details without preserving invariants. The better question is: what operations make sense for this domain object?

## Constructors

### Intuition

Constructors exist because objects often need to establish a valid initial state before they can be used safely.

This is not cosmetic. In C++, an object can represent a real system resource or a nontrivial invariant. Construction is where you set that up.

### Types of Constructors

Common constructor categories include:

- default constructor: creates an object with no arguments
- parameterized constructor: creates an object with explicit setup values
- copy constructor: creates a new object from an existing object
- move constructor: transfers resources from a temporary or expiring object

Copy and move are covered in depth in File 3. For now, focus on the fact that constructors are part of an object's lifecycle contract.

### Initialization Lists

Use member initializer lists when constructing members:

```cpp
class User {
public:
    User(std::string name, int id)
        : name_(std::move(name)), id_(id) {}

private:
    std::string name_;
    int id_;
};
```

Why they exist:

- members are constructed before the constructor body runs
- some types must be initialized rather than assigned later
- initializer lists avoid unnecessary work

Internal detail:

If you assign inside the constructor body, the member is first default-constructed and then assigned to. Initializer lists construct it directly in its final state.

### Practical Usage

- initialize references and `const` members
- pass dependencies explicitly
- guarantee a valid object immediately after construction

### Common Pitfalls

- doing too much work in constructors, especially work that can fail in complex ways
- relying on member declaration order incorrectly; members are initialized in the order they are declared in the class, not the order written in the initializer list
- forgetting `explicit` on single-argument constructors that should not allow implicit conversion

## Destructors

### Intuition

If constructors establish a valid object, destructors clean it up. They exist because C++ objects often manage resources beyond plain memory:

- file descriptors
- mutexes
- sockets
- memory buffers
- database handles

### Example

```cpp
class FileLogger {
public:
    explicit FileLogger(const std::string& path) {
        file_ = std::fopen(path.c_str(), "a");
        if (!file_) {
            throw std::runtime_error("failed to open log file");
        }
    }

    ~FileLogger() {
        if (file_) {
            std::fclose(file_);
        }
    }

private:
    std::FILE* file_ = nullptr;
};
```

### Object Lifecycle Diagram

```mermaid
flowchart LR
    A[Storage acquired] --> B[Constructor runs]
    B --> C[Object is usable]
    C --> D[Destructor runs]
    D --> E[Storage released]
```

### Internal View

When an object goes out of scope, its destructor runs automatically. For class members, destruction happens in reverse order of construction.

This reverse unwinding is critical. It is how C++ guarantees cleanup during normal scope exit and exception propagation.

### Practical Usage

- releasing OS resources
- flushing buffered output
- unlocking a mutex through a guard object
- rolling back or committing scoped transactions

### Common Pitfalls

- performing work in a destructor that can throw exceptions
- forgetting that base and member destructors run automatically
- assuming destruction order across unrelated objects is obvious

## RAII: Resource Acquisition Is Initialization

### Intuition

RAII is one of the most important ideas in C++. It ties resource lifetime to object lifetime.

The idea is simple:

- acquire the resource in the constructor
- release it in the destructor
- let scope determine cleanup

This is why modern C++ code can be both expressive and safe without a garbage collector.

### Why It Exists

Manual cleanup does not scale well in the presence of:

- early returns
- exceptions
- multiple code paths
- partial initialization

RAII turns cleanup into a language-level guarantee rather than a discipline you hope every engineer remembers.

### Example: Mutex Lock Guard

```cpp
void update(std::mutex& mutex, int& value) {
    std::lock_guard<std::mutex> lock(mutex);
    ++value;
}
```

The mutex is locked when `lock` is constructed and automatically unlocked when `lock` goes out of scope.

### Real-World Usage

- file wrappers
- transaction guards
- scoped timers
- custom allocator guards
- lock management

### Misconception to Avoid

“RAII is only about memory.”

No. RAII is about any resource that must be released reliably.

## Inheritance

### Intuition

Inheritance exists to model an “is-a” relationship when a derived type should be usable where a base type is expected.

Used well, inheritance enables substitution and shared interfaces. Used poorly, it creates fragile hierarchies and confusing coupling.

### Example

```cpp
class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Rectangle : public Shape {
public:
    Rectangle(double width, double height)
        : width_(width), height_(height) {}

    double area() const override {
        return width_ * height_;
    }

private:
    double width_;
    double height_;
};
```

### Internal View

A derived object contains a base subobject plus its own members.

```mermaid
flowchart LR
    A[Rectangle object] --> B[Shape base subobject]
    A --> C[width_]
    A --> D[height_]
```

### Practical Usage

- plugin interfaces
- GUI widget hierarchies
- polymorphic simulation entities
- abstractions over hardware or platform-specific implementations

### When Not to Use It

If you only want code reuse, composition is often better. Inheritance should model substitutability, not just convenience.

### Common Pitfalls

- deep hierarchies that are hard to reason about
- using inheritance for implementation reuse where composition is cleaner
- base classes that expose too many assumptions about derived classes
- object slicing when derived objects are copied into base objects by value

## Polymorphism

### Intuition

Polymorphism means “same interface, different implementation.” In C++, there are two major forms:

- runtime polymorphism: usually through virtual functions and base-class references or pointers
- compile-time polymorphism: usually through templates or function overloading

Both matter in interviews and production code, but they solve different problems.

## Runtime Polymorphism

### How It Works

With `virtual` functions, the call target is chosen at runtime based on the dynamic type of the object.

```cpp
void print_area(const Shape& shape) {
    std::cout << shape.area() << '\n';
}
```

If `shape` refers to a `Rectangle`, `Rectangle::area()` runs.

### Internal Mechanics

The exact mechanism is implementation-defined, but the common model is:

- polymorphic objects contain a hidden pointer, often called a vptr
- that pointer refers to a virtual function table, or vtable
- virtual calls use the vtable to resolve the correct function at runtime

```mermaid
flowchart LR
    A[shape reference] --> B[Rectangle object]
    B --> C[vptr]
    C --> D[vtable]
    D --> E[Rectangle::area]
```

### Practical Usage

- runtime-selected backends
- plugin systems
- interface-driven architecture across modules

### Tradeoffs

- extra indirection
- usually one pointer-sized overhead per polymorphic object
- reduced inlining opportunities in some cases

These costs are often acceptable, but they are not free.

## Compile-Time Polymorphism

### Intuition

Sometimes you want generic behavior without runtime overhead. Templates enable this by generating type-specific code at compile time.

```cpp
template <typename T>
T max_value(T a, T b) {
    return a < b ? b : a;
}
```

### Why It Exists

The standard library relies heavily on compile-time polymorphism because it allows generic, highly optimizable code.

### Practical Usage

- STL algorithms and containers
- numeric and serialization libraries
- policy-based design

### Pitfalls

- template errors can be verbose and hard to read
- heavy template usage can increase compile times
- overengineering generic code can make APIs harder to understand

## Object Slicing

### Intuition

Object slicing happens when a derived object is copied into a base object by value. The derived-specific part is discarded.

```cpp
Rectangle rectangle(3.0, 4.0);
Shape shape = rectangle; // invalid here because Shape is abstract, but slicing is the general idea
```

In non-abstract hierarchies, this creates a new base object that no longer behaves like the original derived object.

### Why It Matters

This bug appears when engineers store polymorphic objects by value instead of via pointers or references.

### Rule of Thumb

If you want polymorphism, use references or pointers to the base type, not base objects by value.

## Virtual Destructors

### Intuition

If a class is meant to be used polymorphically, it usually needs a virtual destructor.

Why:

- deleting a derived object through a base pointer must run the derived destructor first
- otherwise cleanup may be incomplete, causing leaks or broken invariants

### Example

```cpp
class Base {
public:
    virtual ~Base() = default;
};
```

### Pitfall

Forgetting this is a classic interview question because it reflects whether you understand object destruction through base interfaces.

## Design Guidance for Real Systems

The most maintainable C++ systems usually follow these patterns:

- small classes with clear ownership boundaries
- composition before inheritance
- constructors that establish valid state immediately
- destructors that make cleanup automatic and boring
- polymorphism only where substitution is genuinely needed

Good C++ OOP is less about building clever hierarchies and more about making lifecycle and resource rules obvious.

## Interview Checkpoints

You should be able to explain:

- what a class object contains at runtime
- why initializer lists matter
- what RAII solves that manual cleanup does not
- the difference between inheritance and polymorphism
- how virtual dispatch works conceptually
- why polymorphic base classes usually need virtual destructors
- what object slicing is and how to avoid it

## What Comes Next

The next file shifts from basic object lifetime to modern ownership and resource management. That is where raw pointers, smart pointers, move semantics, and the Rule of 0 or 3 or 5 all fit together.