This episode covers object-oriented programming in C++: classes and objects, constructors and destructors, access specifiers and encapsulation, inheritance and polymorphism with virtual functions, abstract classes and the interface pattern, as well as smart pointers.

C++ was born from the idea inherited from C: full control over memory. But Stroustrup added something that changed everything — classes. With classes, you can model data and behavior as a single unit called an object.
Episode 6 covers the core of object-oriented programming in C++: how classes are formed, how constructors and destructors manage an object's lifecycle, how encapsulation keeps data integrity, and how inheritance and virtual functions enable polymorphism — one interface for many implementations.
A class is a blueprint; an object is the concrete realization of that blueprint. A class combines data (member variables) and behavior (member functions):
cat > mobil.cpp <<'EOF'
#include <iostream>
#include <string>
class Mobil {
public:
Mobil(std::string merek, int tahun)
: merek_(merek), tahun_(tahun) {}
void info() const {
std::cout << merek_ << " tahun " << tahun_ << "\n";
}
private:
std::string merek_;
int tahun_;
};
int main() {
Mobil avanza("Avanza", 2021);
avanza.info();
}
EOF
g++ -std=c++20 mobil.cpp -o mobil
./mobilThe constructor Mobil(std::string merek, int tahun) initializes members through the initializer list. avanza.info() calls a member function on the avanza object. Const member functions are marked const to guarantee they don't modify the object.
The destructor runs automatically when an object goes out of scope. This is the key to C++-style resource management: if an object opens a file or allocates memory, the destructor closes and frees it. Cleanup happens at a definite, deterministic point — there's no garbage collector. When an object is created, the constructor builds it; when it goes out of scope, the destructor automatically cleans it up. The pattern of "binding a resource to an object's lifetime" is called RAII and becomes the foundation of memory management in episode 7.
Access specifiers control who can access members. public is accessible to anyone, private only from within the class, and protected also from derived classes. Encapsulation uses these to hide internal details:
cat > akun.cpp <<'EOF'
#include <iostream>
class Rekening {
public:
explicit Rekening(double saldoAwal) : saldo_(saldoAwal) {}
void setor(double jumlah) {
if (jumlah > 0) saldo_ += jumlah;
}
double saldo() const { return saldo_; }
private:
double saldo_;
};
int main() {
Rekening r(1000.0);
r.setor(500.0);
std::cout << "Saldo: " << r.saldo() << "\n";
}
EOF
g++ -std=c++20 akun.cpp -o akun
./akunsaldo_ is private, so you can't write r.saldo_ = 999999; from outside. The only way is through the setor method, which validates input — this is where encapsulation's value lies: the data always stays in a consistent state.
Virtual functions allow different behavior for different classes even when called through a base pointer. This is polymorphism — one interface, many implementations. virtual on the base destructor ensures the derived destructor is also run:
cat > hewan.cpp <<'EOF'
#include <iostream>
#include <vector>
#include <memory>
class Hewan {
public:
virtual ~Hewan() = default;
virtual void suara() const = 0;
};
class Anjing : public Hewan {
public:
void suara() const override { std::cout << "Guk guk\n"; }
};
class Kucing : public Hewan {
public:
void suara() const override { std::cout << "Meow\n"; }
};
int main() {
std::vector<std::unique_ptr<Hewan>> koleksi;
koleksi.push_back(std::make_unique<Anjing>());
koleksi.push_back(std::make_unique<Kucing>());
for (const auto& h : koleksi) {
h->suara();
}
}
EOF
g++ -std=c++20 hewan.cpp -o hewan
./hewanvirtual void suara() const = 0 is a pure virtual function — a class that has one is called an abstract class and can't be instantiated. The derived classes Anjing and Kucing must implement it with override. The container holds std::unique_ptr<Hewan> so polymorphism works and memory is managed automatically.
In C++, an interface is formed with an abstract class that only contains pure virtual functions — similar to an interface in Java. This separates the contract from the implementation. Client code only needs to depend on the interface, so implementations can be swapped without touching the callers.
Polymorphism with unique_ptr and make_unique is the modern ownership pattern: pointers are stored as the base type, and when the container is destroyed, all the objects along with their memory are freed automatically.
Warning
If a class has a virtual function, its destructor must be virtual — otherwise, destruction through a base pointer skips the derived class destructor.
Smart pointers wrap dynamic allocation so you don't need manual delete. std::unique_ptr has only one owner, std::shared_ptr shares ownership with reference counting, and std::weak_ptr observes without owning. Since C++11, smart pointers have almost replaced manual new and delete. For example, std::make_unique<Laporan> allocates and constructs the object in a single step. When the owner goes out of scope, the Laporan destructor is called automatically — no manual delete, no leaks.
Here's what to take away:
public, private, and protected control member access.unique_ptr and shared_ptr manage ownership automatically.In the next episode, episode 7, we'll discuss memory and resource management — pointers and references, pointer arithmetic, dynamic allocation with new and delete along with smart pointers, the RAII pattern, and common memory bugs like use-after-free, double-free, and dangling pointers.