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

C++
#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';
}
Output
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:

C++
class BankAccount {
public:
    double balance() const { return balance_; }
private:
    double balance_ = 0.0;
};

int main() {
    BankAccount acc;
    acc.balance_ = 1'000'000;
    return 0;
}
Compiler error
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:

C++
#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() {}
Compiler error
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 :::

C++
#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';
}
Output
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:

C++
#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';
}
Output
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:

C++
#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';
}
Output
1 2 1

Try it yourself

Write a Thermostat class that keeps its target temperature between 10 and 30 degrees, whatever is requested.

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
requested 22.5, set 22.5
requested 45, set 30
requested -5, set 10