BookNotesSlidesP-sets Syllabusdraft · v0.2.49.1
◀▲⌂▶
💬Comments welcome. To leave a note, select any text and click the note / highlight button that pops up — or open the panel with the tab at the top-right (‹). Notes are visible only inside our private review group.

45.1 C++ for Python programmers⧉

The problem sets in this book are written in C++, and if Python is your home language this appendix is the bridge. You are not expected to arrive knowing C++: the early psets ramp you up, and what follows is the map of what changes.

Why C++ at all? It is compiled and close to the metal, so the per-pixel loops that image processing lives on can run far faster than equivalent scalar Python loops; the gain depends on the workload and compiler, and is not a comparison with optimized NumPy kernels. It is still the standard where performance matters, in graphics and imaging, and it is the language Chapter 29.4, Halide programming plugs into for the last chapters. The price is bluntness: more typing, fewer guardrails, and memory you sometimes manage yourself. The good news is that for this book you stay in a small, safe corner of the language.

45.1.1 What changes, coming from Python⧉

45.1.2 A first taste, side by side⧉

Brighten an image by a constant factor, written as a function in each language. In Python the body is a single NumPy broadcast:

def brighten(im, factor):
    return im * factor

Assume floating-point NumPy inputs, with import numpy as np. In C++ with the course's Image class it is an explicit loop, and every difference above shows up at once:

Image brighten(const Image &im, float factor) {
    Image out(im.width(), im.height(), im.channels());
    for (int c = 0; c < im.channels(); c++)
        for (int y = 0; y < im.height(); y++)
            for (int x = 0; x < im.width(); x++)
                out(x, y, c) = im(x, y, c) * factor;
    return out;
}

Types on everything, the loop written out, const Image &im to pass the input by reference without copying it or letting the function change it, and semicolons throughout. The vectorization is gone, but the compiled loop is fast.

45.1.3 A few more comparisons⧉

The same algorithm three ways (the C++ clamp example needs C++17 and <algorithm>): plain Python with the explicit loops C++ makes you write, the C++ itself, and the vectorized NumPy one-liner. The shading marks what C++ adds over the plain-Python loops, only the static types, the punctuation, and std::; the loops themselves are identical. Switch tabs, and copy any of them:

Listing 45.1.1 · Clamp to the valid rangeshaded = differs from Pythonembed
Image clamp01(const Image &;im) {
    Image out(im.width(), im.height(), im.channels());
    for (int c = 0; c <; im.channels(); c++)
      for (int y = 0; y <; im.height(); y++)
        for (int x = 0; x <; im.width(); x++)
          out(x, y, c) = std::clamp(im(x, y, c), 0.0f, 1.0f);
    return out;
}
Listing 45.1.2 · Convert to grayscale (luma)shaded = differs from Pythonembed
Image luminance(const Image &;im) {
    Image out(im.width(), im.height(), 1);          // one output channel
    for (int y = 0; y <; im.height(); y++)
      for (int x = 0; x <; im.width(); x++)
        out(x, y, 0) = 0.299f * im(x, y, 0)
                     + 0.587f * im(x, y, 1)
                     + 0.114f * im(x, y, 2);
    return out;
}
Listing 45.1.3 · Blend two imagesshaded = differs from Pythonembed
Image blend(const Image &;a, const Image &;b, float t) {
    Image out(a.width(), a.height(), a.channels());
    for (int c = 0; c <; a.channels(); c++)
      for (int y = 0; y <; a.height(); y++)
        for (int x = 0; x <; a.width(); x++)
          out(x, y, c) = (1.0f - t) * a(x, y, c) + t * b(x, y, c);
    return out;
}
Listing 45.1.4 · Average pixel value (a reduction)shaded = differs from Pythonembed
float mean(const Image &;im) {
    float total = 0.0f;
    for (int c = 0; c <; im.channels(); c++)
      for (int y = 0; y <; im.height(); y++)
        for (int x = 0; x <; im.width(); x++)
          total += im(x, y, c);
    return total / (im.width() * im.height() * im.channels());
}

The luminance example retains the course's grayscale weights and function name: on encoded RGB these weights compute a luma-like quantity, not physical linear-light luminance. Inputs are nonempty; RGB examples need three channels, and blending requires matching shapes.

Across all of them the same shapes recur in the C++: const Image & for every input, im.width()/height()/channels() as the loop bounds, float literals with the f suffix, and a fresh Image out(...) to fill and return (or, for a reduction, a plain float). The plain-Python column makes the real point: the C++ loop is not exotic, it is the loop you would write in Python the moment you step off NumPy's vectorized path, with static types and braces bolted on. Once those are muscle memory the translation is mechanical, and making the loop fast is the compiler's job.

45.1.4 Files and compilation⧉

A C++ project is split in two. The header Image.h declares the class and the function signatures; the implementation Image.cpp defines them. To use another module you #include "Image.h", which literally pastes its declarations in. Because a header can be included along several paths, each one is wrapped in an include guard, an #ifndef / #define / #endif sandwich that stops it being pasted twice; it looks archaic, and the template files already have it. A build tool (make, or CMake) reads a build file listing the sources and runs the compiler, so you compile the whole project with one command.

Two syntax traps trip people early. In a .cpp file the scope operator :: says which class a definition belongs to, as in float Image::operator()(int x, int y, int c), because one file can implement several classes. And a class definition needs a semicolon after its closing brace; forget it and the error surfaces, confusingly, in the next file.

45.1.5 Value, reference, and const⧉

This is the shift that trips Python programmers most, because Python hides it. How you declare a function's parameter decides whether it sees a copy or the original, and whether it may change it.

A trailing const on a method, int width() const;, is the same promise from the other side: the method will not mutate the object, so getters are marked const and can be called on read-only inputs.

45.1.6 Memory, briefly⧉

An ordinary local object has automatic storage duration and is destroyed when its scope ends; its owned pixel buffer may live in dynamically allocated storage; Image im(w, h, c); is all you need, with no new. Heap allocation with new must be matched by delete, which is where leaks come from, so we lean on std::vector, which owns and frees its own storage. The one sharp edge to remember: array access is unchecked, so v[i] with i out of range does not raise an exception. It silently reads or corrupts unrelated memory, or crashes with a segmentation fault, often far from the actual mistake.

45.1.7 Images in C++⧉

The course wraps an image in a class holding a flat std::vector<float> (often normalized to [0, 1], but intermediate values can lie outside that range) together with its width, height, and channel count. A vector has a single index, so a 3-D image is packed into 1-D by strides. The book, like Halide, stores it plane by plane (all of one channel, then the next):

Algorithm 45.1.1
index of (x, y, c) = c * width * height + y * width + x

An accessor im(x, y, c) hides that arithmetic. It takes x first, unlike NumPy's usual [y, x, c] indexing, and keeps the code consistent with Halide later. The same layout and the reasoning behind it are in Chapter 8.1, Image representation.

45.1.8 Debugging, and the parts that hurt⧉

The recurring frustration of C++ is that errors surface far from their cause: the missing class semicolon breaks the next file, an out-of-bounds write crashes somewhere unrelated, a segmentation fault points at the symptom, not the bug. A few habits from the course make it bearable.

45.1.9 Where to go next⧉

The first problem set (Chapter 45.3, Problem Set 0 — Environment and C++ basics) is a warm-up by design: set up the environment, compile the template, write a few lines with std::vector, and do a brightness or contrast point operation. The psets after it build the Image class, point and neighborhood operations, and onward. For a fuller Python-to-C++ walkthrough, the course points to Goldwasser and Letscher's Python to C++ guide and the cpp4py tutorial.