Classes and Objects
A class bundles data with the functions that work on it, and controls which parts the outside world may touch. Classes let you build types that behave like built-in ones — a BankAccount that can't go below zero, a Date that's always valid — so the rest of the program can rely on them.
A first class
#include <iostream> #include <string> class BankAccount { public: void deposit(double amount) { if (amount > 0) balance_ += amount; } bool withdraw(double amount) { if (amount <= 0 || amount > balance_) return false; balance_ -= amount; return true; } double balance() const { return balance_; } private: double balance_ = 0.0; }; int main() { BankAccount acc; acc.deposit(100); std::cout << std::boolalpha << acc.withdraw(30) << ' ' << acc.withdraw(500) << '\n'; std::cout << "balance: " << acc.balance() << '\n'; }
true false balance: 70
An object (acc) is an instance of a class. Functions inside the class are member functions (methods), called with a dot. The trailing underscore on balance_ is a common naming convention for private data members.
public and private
public members form the class's interface; private members are implementation details that only the class's own functions can use. Outside code can't break the rules by changing the balance directly:
class BankAccount { public: double balance() const { return balance_; } private: double balance_ = 0.0; }; int main() { BankAccount acc; acc.balance_ = 1'000'000; return 0; }
main.cpp:10:9: error: 'balance_' is a private member of 'BankAccount'
This is encapsulation: the class guarantees its own invariants (here, "the balance is never negative"), so no other code has to.
class vs. struct
The only technical difference is the default: members of a struct are public unless stated otherwise, members of a class are private. By convention, use struct for simple bundles of data with no rules to protect (lesson 20) and class when there are invariants.
const member functions
A member function marked const promises not to modify the object. Only const functions can be called on a const object or through a const reference — so mark every function that doesn't change state:
#include <string> class Label { public: std::string text() const { return text_; } void set_text(std::string t) { text_ = std::move(t); } private: std::string text_; }; void show(const Label& label) { label.text(); // fine: const function label.set_text("oops"); // error: set_text isn't const } int main() {}
main.cpp:13:5: error: 'this' argument to member function 'set_text' has type 'const Label', but function is not marked const
Defining member functions outside the class
In larger programs, the class definition (in a header) declares the functions and a source file defines them, using the class name and :::
#include <iostream> class Rectangle { public: void resize(double w, double h); double area() const; double perimeter() const; private: double width_ = 1, height_ = 1; }; void Rectangle::resize(double w, double h) { if (w > 0 && h > 0) { width_ = w; height_ = h; } } double Rectangle::area() const { return width_ * height_; } double Rectangle::perimeter() const { return 2 * (width_ + height_); } int main() { Rectangle r; r.resize(4, 2.5); std::cout << r.area() << ' ' << r.perimeter() << '\n'; }
10 13
The this pointer
Inside a member function, this points to the object the function was called on. It's mostly implicit, but useful for returning the object itself so calls can be chained:
#include <iostream> #include <string> class Message { public: Message& add(const std::string& part) { text_ += part; return *this; } const std::string& text() const { return text_; } private: std::string text_; }; int main() { Message m; m.add("Hello").add(", ").add("world"); std::cout << m.text() << '\n'; }
Hello, world
Objects are values
Like int and std::string, objects can be copied, stored in containers and passed to functions — each copy independent of the others:
#include <iostream> #include <vector> class Counter { public: void increment() { ++count_; } int count() const { return count_; } private: int count_ = 0; }; int main() { Counter a; a.increment(); Counter b = a; // copy b.increment(); std::vector<Counter> counters(3); counters[1].increment(); std::cout << a.count() << ' ' << b.count() << ' ' << counters[1].count() << '\n'; }
1 2 1
Try it yourself
Write a Thermostat class that keeps its target temperature between 10 and 30 degrees, whatever is requested.
requested 22.5, set 22.5 requested 45, set 30 requested -5, set 10