Object-Oriented Programming · Lesson 10 of 33
Classes and Objects in C++
A class bundles data and the functions that work on it into one type. A bank account has a balance and operations like deposit and withdraw; a class lets you make the balance private so it can only change through those operations. Classes are the heart of C++ – and so is RAII, the idea that makes C++ resource management safe.
In this lesson
- Defining a class: members, public and private
- Constructors and member initialiser lists
- Destructors and RAII
- const member functions and static members
A first class
#include <iostream>
#include <stdexcept>
#include <string>
class BankAccount {
public: // usable from outside
BankAccount(std::string owner, double opening) // constructor
: owner_(std::move(owner)), balance_(opening) { // member initialiser list
if (opening < 0) throw std::invalid_argument("opening balance can't be negative");
}
void deposit(double amount) {
if (amount <= 0) throw std::invalid_argument("deposit must be positive");
balance_ += amount;
}
bool withdraw(double amount) {
if (amount > balance_) return false; // the rule lives in one place
balance_ -= amount;
return true;
}
double balance() const { return balance_; } // const: promises not to modify the object
const std::string& owner() const { return owner_; }
private: // only member functions can touch these
std::string owner_;
double balance_;
};
int main() {
BankAccount acc("Asha", 5000); // an object of the class
acc.deposit(1500);
std::cout << acc.owner() << ": " << acc.balance() << '\n';
std::cout << "withdraw 9000? " << (acc.withdraw(9000) ? "ok" : "refused") << '\n';
std::cout << "withdraw 2000? " << (acc.withdraw(2000) ? "ok" : "refused") << '\n';
std::cout << "balance " << acc.balance() << '\n';
// acc.balance_ = 1e9; // error: balance_ is private
return 0;
}
Asha: 6500 withdraw 9000? refused withdraw 2000? ok balance 4500
What the class guarantees
- public interfacedeposit, withdraw, balance, owner – the only ways in
- private dataowner_ and balance_ – can't be changed directly
- invariantthe balance is never negative, because every change goes through checked functions
This is encapsulation: the object protects its own rules. The trailing underscore on member names is a common convention to tell members from parameters.
Constructors
#include <iostream>
#include <string>
class Point {
public:
Point() = default; // default constructor: uses the member defaults
Point(int x, int y) : x_(x), y_(y) {} // initialise members directly
explicit Point(int both) : Point(both, both) {} // delegating; explicit = no silent conversion
std::string str() const { return "(" + std::to_string(x_) + ", " + std::to_string(y_) + ")"; }
private:
int x_ = 0; // default member initialisers
int y_ = 0;
};
int main() {
Point origin;
Point p{3, 4};
Point diag{7};
std::cout << origin.str() << ' ' << p.str() << ' ' << diag.str() << '\n';
// Point q = 5; // error thanks to explicit
return 0;
}
(0, 0) (3, 4) (7, 7)
Initialise in the list, not the body
Members are constructed before the constructor body runs, so assigning them in the body constructs them twice. const members and references can only be set in the initialiser list. Members are initialised in the order they're declared in the class, not the order you list them.
Destructors and RAII
A destructor (~ClassName()) runs automatically when an object goes out of scope. RAII ("Resource Acquisition Is Initialisation") uses this: acquire a resource in the constructor, release it in the destructor – and it can never be forgotten, even if an exception is thrown.
#include <iostream>
#include <string>
class Timer { // a tiny RAII class
public:
explicit Timer(std::string name) : name_(std::move(name)) {
std::cout << "start " << name_ << '\n';
}
~Timer() { std::cout << "stop " << name_ << '\n'; }
private:
std::string name_;
};
void work() {
Timer t("work");
for (int i = 0; i < 2; i++) {
Timer step("step " + std::to_string(i));
} // step destroyed here, each iteration
std::cout << "finishing work\n";
} // t destroyed here
int main() {
work();
std::cout << "back in main\n";
return 0;
}
start work start step 0 stop step 0 start step 1 stop step 1 finishing work stop work back in main
RAII is everywhere in C++
std::vector frees its memory, std::ifstream closes its file, std::unique_ptr deletes its object and std::lock_guard releases its mutex – all in their destructors. That's why idiomatic C++ almost never calls delete, fclose or unlock by hand.
static members
#include <iostream>
class Ticket {
public:
Ticket() : id_(++issued_) {}
int id() const { return id_; }
static int issued() { return issued_; } // belongs to the class, not an object
private:
int id_;
inline static int issued_ = 0; // one counter shared by all tickets (C++17)
};
int main() {
Ticket a, b, c;
std::cout << a.id() << ' ' << b.id() << ' ' << c.id() << " issued " << Ticket::issued() << '\n';
return 0;
}
1 2 3 issued 3
struct vs class
In C++ the only difference is the default access: members of a struct are public by default, members of a class private. Convention: use struct for simple data bundles with no rules to protect (a point, a pair of scores), and class when there's an invariant to guard.
Practice
Try it yourself
Write a
Rectangleclass with width and height (both must be positive), and const methodsarea()andperimeter().Show solution
#include <iostream> #include <stdexcept> class Rectangle { public: Rectangle(double w, double h) : w_(w), h_(h) { if (w <= 0 || h <= 0) throw std::invalid_argument("sides must be positive"); } double area() const { return w_ * h_; } double perimeter() const { return 2 * (w_ + h_); } private: double w_, h_; }; int main() { Rectangle r(4, 2.5); std::cout << "area " << r.area() << ", perimeter " << r.perimeter() << '\n'; return 0; }area 10, perimeter 13
Quick check
What does const after a member function mean?
The function doesn't modify the object, so it can be called on const objects.
When does a destructor run?
Automatically when the object's lifetime ends – e.g. when a local object goes out of scope.
What is RAII?
Tying a resource's lifetime to an object: acquire in the constructor, release in the destructor.
Summary
- Classes combine private data with a public interface that protects invariants.
- Initialise members in the constructor's initialiser list; mark single-argument constructors explicit.
- Destructors make RAII possible; static members are shared by all objects.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.