A cookie cutter being pressed into rolled-out dough, leaving identical shapes. Photo · George Pagan III / Unsplash
The cookie cutter is the class. Each cookie is an object. One cutter, many cookies, all the same shape — each free to be a different colour.

The single biggest source of confusion when people first meet object-oriented programming is the difference between a class and an object. Once it clicks, it never stops being obvious. Until then, every code sample is a riddle.

So we go slowly today. The class is a description. An object is one specific living instance of that description. The cookie cutter is the class. The cookies are objects. There is exactly one cutter and as many cookies as you bake — all sharing the same shape, each one free to differ in the details.

The two halves

A C++ class declaration is a recipe. It says what an object of this kind looks like — what data it carries, what it can do. The class is information about a kind of thing. It does not, by itself, exist in any meaningful sense at runtime. It is a statement to the compiler.

An object is a living instance of that class — a chunk of memory shaped according to the class's recipe, with the recipe's methods callable on it. Every object you create has its own copy of the data fields. Methods are shared (the class declares them once and every object uses the same code), but data is per-object.

A worked example — Point

// the CLASS — a description of "what does a Point look like"
class Point {
public:
    double x;
    double y;

    double distance_from_origin() const {
        return sqrt(x * x + y * y);
    }
};

int main() {
    // OBJECTS — actual living Points
    Point a;  a.x = 3;  a.y = 4;
    Point b;  b.x = 0;  b.y = 0;

    std::cout << a.distance_from_origin();  // 5
    std::cout << b.distance_from_origin();  // 0
}

The class Point is declared once. The objects a and b are created twice, each with its own x and y. The method distance_from_origin exists once, but every time you call it through a particular object, the method uses that object's data. a.distance_from_origin() uses a's 3 and 4. b.distance_from_origin() uses b's 0 and 0. Same code, different objects, different answers.

Methods get a hidden first argument

When you call a.distance_from_origin(), what's actually happening, under the hood, is that the compiler rewrites the call to pass &a as a hidden first argument. Inside the method body, the special pointer this is that argument. x and y are shorthand for this->x and this->y.

// what you write:
double distance_from_origin() const {
    return sqrt(x * x + y * y);
}

// what the compiler effectively builds:
double Point__distance_from_origin(const Point *this) {
    return sqrt(this->x * this->x + this->y * this->y);
}

This is exactly the C-style "function takes a pointer to a struct" pattern from last week, with the compiler hiding the boilerplate. There's no magic happening at runtime. The only difference is who's writing the boilerplate — you, or the compiler. (The compiler does it more reliably and never forgets.)

const methods — promising not to mutate

Notice the const after the method name: distance_from_origin() const. It tells the compiler "this method does not modify the object". Inside the method, this is treated as const Point * — you can read fields, but you can't write them. Try, and the compiler will refuse.

This is enormously useful. When you have a const Point& p, you can only call its const methods. If a method wants to be safe to call on a const object — and most "getter" methods should — mark it const. The discipline keeps you honest, lets the compiler optimise more aggressively, and makes the contract explicit at the call site.

Class vs struct

C++ has both class and struct. They're nearly identical. The only practical difference: members of a class are private by default, members of a struct are public. Convention has settled on:

You can write a class using either keyword and it'll work. Code reviewers will gently push you toward the convention. C++ has lots of these "two ways to write the same thing, with a convention attached" situations; you'll get used to them.

Where do objects live?

Three options, and you should know all three:

The whole topic of "where the bytes physically live" matters for performance, for cache behaviour, and for the lifetime guarantees you can offer — all things that come up later in the phase.

Why this matters for AI

Open the PyTorch source. Find the Tensor class. It's a class with private data members holding the storage pointer, the dimensions, the strides, the device. It has methods like .size(), .dtype(), .cuda(). Every Python tensor you've ever touched is, on the C++ side, an object of type at::Tensor — and the entire deep-learning industry is built on the methods of that one class.

Same for nn::Module. Optimizer. DataLoader. They're all classes. Once you see them as cookie cutters with cookies, they stop being intimidating — they're just bundles of fields and methods.

A class is not a thing. It is the description of a thing. The object is the thing.

Try it yourself

What's next

Next week we lock the front door. Right now anyone can read or write a.x directly — there's no protection. The whole point of object-orientation is that you shouldn't let outsiders touch your fields. Next week's keyword is private.

Week 20 is The Black Box — encapsulation, the public/private split, and the contract between a class and its callers.

Photo credit

Photo free under the Unsplash license. Cookies · George Pagan III.