OOP
Classes and Objects
Build your own types with C++ classes, constructors, destructors, and access control.
Classes in C++
A class defines a blueprint for objects. It encapsulates data (member variables) and behavior (member functions/methods).
Access Specifiers
public: Accessible from anywhereprivate: Only accessible within the class (default for classes)protected: Accessible within the class and derived classes
Constructors and Destructors
- Constructor: Called when an object is created. Initializes member variables.
- Destructor: Called when an object is destroyed. Frees resources.
The Rule of Three/Five
If you define a destructor, copy constructor, or copy assignment operator, you likely need to define all three (or five with move semantics in C++11).
Example
#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
string owner;
double balance;
int accountNumber;
static int nextAccountNumber; // shared across all instances
public:
// Constructor with initializer list
BankAccount(const string& ownerName, double initialBalance)
: owner(ownerName), balance(initialBalance),
accountNumber(nextAccountNumber++) {
cout << "Account #" << accountNumber << " created for " << owner << endl;
}
// Destructor
~BankAccount() {
cout << "Account #" << accountNumber << " closed" << endl;
}
// Member functions
void deposit(double amount) {
if (amount > 0) balance += amount;
}
bool withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
return true;
}
return false;
}
// Getter (const method - doesn't modify the object)
double getBalance() const { return balance; }
string getOwner() const { return owner; }
// Static method
static int getTotalAccounts() { return nextAccountNumber; }
};
// Initialize static member
int BankAccount::nextAccountNumber = 1001;
int main() {
BankAccount alice("Alice", 1000.0);
BankAccount bob("Bob", 500.0);
alice.deposit(250.0);
cout << alice.getOwner() << " balance: " << alice.getBalance() << endl;
if (!bob.withdraw(600.0)) {
cout << "Insufficient funds!" << endl;
}
cout << "Total accounts: " << BankAccount::getTotalAccounts() << endl;
return 0;
}#include <iostream>
#include <string>
using namespace std;
class BankAccount {
private:
string owner;
double balance;
int accountNumber;
static int nextAccountNumber; // shared across all instances
public:
// Constructor with initializer list
BankAccount(const string& ownerName, double initialBalance)
: owner(ownerName), balance(initialBalance),
accountNumber(nextAccountNumber++) {
cout << "Account #" << accountNumber << " created for " << owner << endl;
}
// Destructor
~BankAccount() {
cout << "Account #" << accountNumber << " closed" << endl;
}
// Member functions
void deposit(double amount) {
if (amount > 0) balance += amount;
}
bool withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
return true;
}
return false;
}
// Getter (const method - doesn't modify the object)
double getBalance() const { return balance; }
string getOwner() const { return owner; }
// Static method
static int getTotalAccounts() { return nextAccountNumber; }
};
// Initialize static member
int BankAccount::nextAccountNumber = 1001;
int main() {
BankAccount alice("Alice", 1000.0);
BankAccount bob("Bob", 500.0);
alice.deposit(250.0);
cout << alice.getOwner() << " balance: " << alice.getBalance() << endl;
if (!bob.withdraw(600.0)) {
cout << "Insufficient funds!" << endl;
}
cout << "Total accounts: " << BankAccount::getTotalAccounts() << endl;
return 0;
}$ g++ main.cpp -o main && ./mainWhere You'll See This in the Real World
A class that owns a resource, sets it up when it is created and releases it when it goes out of scope, is the single most load-bearing idea in C++. It has a name — RAII — and almost every C++ library you will ever use is built on it:
- Files and network connections. Something has to close the handle even when the code in between throws an exception. Wrapping it in an object means the closing happens on the way out, whatever route the program takes.
- Locks in multithreaded code. A lock that is not released deadlocks the program. Making the lock an object that unlocks itself when it goes out of scope is why std::lock_guard exists and why you almost never call unlock() by hand.
- Graphics and audio resources. A texture on the GPU or a buffer on the sound card has to be handed back when a frame or a track is done. Engines wrap each one in a small class so the release cannot be forgotten.
- Database sessions. A connection borrowed from a pool must go back to the pool, and an unfinished transaction must roll back. Both are the destructor's job.
The detail that bites in production is the one this lesson introduces last: if a class owns something, the compiler-generated copy will copy the handle and two objects will release the same resource. That is why real classes either delete the copy, or write it, or hold the resource in a type that already knows the answer.