The class
A constructor is a member function with the same name as its class and no return type. C++ calls it for you every time an object of the class is created. Its job: give the new object a valid starting state.
The part after the colon, : id(c), is the member initializer list. It sets each member while the object is being built, before the constructor's body runs. Here the member id gets the character passed in.
The destructor is named ~Cup: the class name with a tilde in front. It takes no arguments, returns nothing, and a class has only one. C++ calls it automatically when an object's lifetime ends, so it can clean up.
Lifetimes
main starts. Cup a('A'); creates an object on the stack: memory is set aside for a, then the constructor runs with c = 'A'. It sets id to 'A' and prints fill A. Now a is alive.
A pair of braces { } opens an inner block, which is a new scope. Cup b('B'); is created inside it, so its constructor runs and prints fill B. Now a and b are both alive.
At the block's closing }, b goes out of scope and its lifetime ends. C++ calls b's destructor automatically: it prints wash B, then b's memory is given back. You never write a call to ~Cup yourself.
Cup c('C'); creates a third object and prints fill C. a is still alive, because main's block has not ended. An object on the stack lives exactly as long as the block that declares it.
At main's closing }, c and a both go out of scope. Destructors run in the reverse order of construction, newest first: wash C, then wash A. The object made last is destroyed first.
Why it matters
Because the destructor always runs when scope ends, a class can take a resource in its constructor and give it back in its destructor. This pattern is called RAII. std::string, std::vector and std::ifstream work this way, so they clean up for you.
What happens
;; step 1/9 · ctor
3 Cup(char c) : id(c) {
4 std::printf("fill %c\n", id);
5 }
Cup(char c) same name as the class, no return type
takes a parameter c, like any function
runs automatically, once, when a Cup is created
you never call it by name: Cup a('A'); calls it
The two cards are one real file, cups.cpp, which starts with #include <cstdio> (for std::printf). Build it with g++ -std=c++20 -Wall -Wextra cups.cpp -o cups and run ./cups: it prints fill A, fill B, wash B, fill C, wash C, wash A. The figure gives each object its own slot on the stack. The real layout is up to the compiler, which may reuse b's memory for c once b is gone.