Skip to content

Module 1 — Concepts and Explanations

Read this with a compiler open. Every section ends with code, and the code is the point — the paragraphs exist to tell you what to look for in it.


1. What actually happens when I press compile?

Four stages, and the course's one command runs them all:

g++ -std=c++17 -Wall -Wextra main.cpp -o program
What you wrote What the compiler produces What it needs from you
A .cpp file — a translation unit An object file (.o) Correct C++
Several object files One executable Every name to have exactly one definition
  1. Preprocessing. #include <iostream> pastes the contents of that header into your file. #define is expanded. The result is a single, large source file.
  2. Compilation. That source is translated into machine code for one file, producing an object file. Errors here are syntax, type and name errors.
  3. Linking. The object files, plus the standard library, are combined and every referenced name is matched to a definition. Errors here are missing things errors — "you used this, but nobody ever defined it".
  4. Running. Now, and only now, does behaviour matter.

You can stop after stage 2 to inspect what the compiler thinks:

g++ -std=c++17 -Wall -Wextra -c main.cpp     # produces main.o, no executable

Why this matters before you write a single class: the two kinds of failure you will meet all course long come from different stages, look different, and are fixed in different places. Section 2 makes the difference concrete.


2. Why does my program fail twice, in two different ways?

Three failures, three messages, three fixes. Learn to tell them apart now, because every module after this assumes you can.

Failure When A typical message Where the mistake is
Compile error Stage 2 error: expected ';' before '}' token In the code the compiler is reading
Link error Stage 3 undefined reference to 'area(double, double)' In a missing definition, or a file not named in the command
Runtime error Stage 4 Anything from wrong output to a crash In your logic or your memory use

The link error is the one beginners meet last and understand least, because the code looks finished. Here it is deliberately:

// broken.cpp — this compiles, and then fails to link
#include <iostream>

double area(double width, double height);   // declared (a promise)

int main() {
    std::cout << area(3.0, 4.0) << '\n';    // used
}
// ... and never defined. The promise was not kept.
/tmp/cc7x2LqF.o: in function `main':
broken.cpp:(.text+0x1e): undefined reference to `area(double, double)'
collect2: error: ld returned 1 exit status

The odd name at the start, cc7x2LqF.o, is the compiler's own temporary object file: its name is random and will differ on your machine, and nothing in your source produces it. The message names main as the location — but main is not wrong. It is the caller. The mistake is an absent definition. 04-debugging.md turns this into a routine thirty-second diagnosis.


3. Why does std:: keep appearing in front of everything?

The standard library has thousands of names: cout, string, vector, sort, min, max, count, distance, swap. Your program will invent its own names too. Without somewhere to file them, collisions are guaranteed.

A namespace is that filing system:

#include <iostream>

namespace library {
int count = 3;                  // library::count
}

int count = 7;                  // the global ::count

int main() {
    std::cout << "mine: " << count << '\n';            // 7
    std::cout << "theirs: " << library::count << '\n'; // 3
}

std::cout says "the cout that lives in std". Everything in the standard library lives in std, which is why std:: is everywhere.

Why the course avoids using namespace std;. In a small file it is harmless, and in a large program it is a trap:

#include <iostream>
#include <algorithm>            // provides std::count

// using namespace std;   <-- uncomment this one line

int count = 0;                  // and now this becomes ambiguous

int main() {
    std::cout << count << '\n';
}
error: reference to 'count' is ambiguous

In a header, the damage is worse: everything that includes your header inherits the collision. So the rule is: type std::, or be explicit about which names you actually want (using std::cout;) — and never using namespace std; in a header.

You will see using namespace std; in other tutorials

It is not a lie, and it is not good practice. It is a shortcut that works in 30-line teaching examples and causes real bugs in 3,000-line programs. The course uses std:: everywhere so that your eye learns to read it.


4. Why does my program get harder to change as it grows?

This is the question the whole course answers, so here it is as a concrete failure rather than a claim.

A small library keeps four items. Three parallel arrays, four functions:

// the shape of it — the full program is examples/module-01/01-procedural-baseline/main.cpp
std::string g_titles[4] = {"C++ Primer", "Effective Modern C++",
                           "Clean Code", "UML Distilled"};
int g_ids[4] = {1001, 1002, 1003, 1004};
bool g_onLoan[4] = {false, true, false, true};

It works. So where is the fragility? In four specific places:

1. The index is the only link between the arrays. g_titles[2] and g_ids[2] describe the same item because you keep them aligned. Nothing checks it. A function that sorts one array and forgets the others silently corrupts every record.

2. Every operation has to be handed the data it needs.

bool findById(int id, int& foundIndex);      // the answer comes out of a parameter
bool lend(int id, const std::string& by);    // returns false for four different reasons

lend returns false when the identifier is unknown and when the item is already out. The caller cannot tell the two apart without asking again.

3. The rules live in the writer's head. "An item is on loan or available, never both." Where is that written down? In the reader's memory of the code. lend must remember to check it; returnItem must remember to check it; the printer must remember not to print both.

4. Everything is reachable. g_onLoan[2] = false; compiles from anywhere in the file, and nothing connects it to the loans that were recorded elsewhere.

Now extend it. Add a publicationYear field. Count the places you must touch:

Place Why
the array declaration a fourth array
addItem a new parameter, and a new assignment
the menu handler to read the new value from input
printItem to show it
the search that returns by index because the caller now assumes it exists
every test and every caller because the parameter list changed

Six places, for one field. That number is the argument for everything that follows.


5. What would it look like if the data carried its own behaviour?

The same catalogue, with the data and the rules in one place — the second of the two Module 1 programs, examples/module-01/03-first-class/main.cpp:

class Catalogue {
public:
    void addItem(int id, const std::string& title) {
        ids_[count_] = id;
        titles_[count_] = title;
        onLoan_[count_] = false;
        ++count_;
    }

    bool lendItem(int id) {
        const int index = indexOf(id);
        if (index < 0 || onLoan_[index]) {
            return false;
        }
        onLoan_[index] = true;
        return true;
    }
    // ...
private:
    int count_ = 0;
    int ids_[kMaxItems] = {0};
    std::string titles_[kMaxItems];
    bool onLoan_[kMaxItems] = {false};
};

Three differences, and they are the whole course in miniature:

  • The data is one thing now, not four things that must stay aligned. addItem updates all of it in one place, so it cannot get out of step.
  • The rules have a home. "Available or on loan, never both" is enforced inside the type, by the only operations that can change it.
  • Callers speak the system's language. catalogue.lendItem(1001) instead of lend(1001, titles, ids, onLoan, count).

Does the change get cheap? Adding publicationYear now touches: the private data, addItem, and the printer. And every caller is unaffected, because the parameter list did not change.


6. What does "object-oriented" actually promise?

Four ideas, each of which solves one of the problems in section 4. This is a preview — the module named beside each one is where it becomes real work.

Pillar The problem it solves Where it is taught
Abstraction Callers should ask for what the system means, not how it is stored Module 4
Encapsulation A rule must be impossible to break from outside, not merely discouraged Module 4
Inheritance Two kinds of thing genuinely share a description, and duplication would drift Module 6
Polymorphism One loop must handle every kind of thing without asking which kind it is Module 7

examples/module-01/04-pillars-preview/main.cpp shows all four in about eighty lines. Do not try to master it — read it as a map of where you are going:

std::vector<const LendableItem*> items{&book, &dvd};
for (const LendableItem* item : items) {
    std::cout << "   " << item->describe() << '\n';   // one loop, two kinds
}

A caution about "object-oriented". It does not mean "use classes". A 30-line script that converts a file format needs no classes at all, and adding them makes it longer without making it better. Objects earn their place when there is state with rules about it that must survive being passed around. That is the test this course keeps applying.


7. Where do the classes come from?

Not from the syntax. From the problem description.

In the description Becomes
A noun that has data attached — book, member, loan A type
A verb the system performs — borrow, return, search An operation on a type
A rule — "a member may hold at most three items" An invariant, enforced by an operation
A noun that is only a value — a due date, an identifier A simple type, or a plain value

Applied to the campus library system this course builds across sixteen labs:

Candidate Responsibility, in one sentence Type or operation?
Book An item that can be lent, with a title and an identifier type
Member A borrower with a name and a current holding type
Loan The record that a particular member has a particular item type
Catalogue Holds the items and members; answers questions about them type
lending an item The system's verb: check, mark, record operation on Catalogue
"at most three items" The rule that must hold for every member invariant

Two checks before you commit. First: can you state the type's responsibility in one sentence without "and"? If not, it is probably two types. Second: name one thing you deliberately did not make a type. A design where everything becomes a class is not a design.

This analysis is Lab 1's last step

Do it on paper before you write any code. In Lab 2 you will turn these candidates into real types, and a vague analysis now costs an hour then.


8. What do I already need to be able to read?

Three things, at reading level. Module 10 gives them the depth they deserve.

#include <iostream>

int main() {
    int value = 5;
    int* pointer = &value;      // `pointer` holds the ADDRESS of `value`
    int& alias = value;         // `alias` IS another name for `value`

    *pointer = 7;               // write through the pointer
    std::cout << value << '\n'; // 7 — same object
    alias = 9;                  // write through the reference
    std::cout << value << '\n'; // 9 — same object

    int copy = value;           // a separate object
    copy = 0;
    std::cout << value << '\n'; // 9 — the copy changed, not `value`
}
Idea Written Means Watch for
Value int n Its own object Changes here do not affect the caller
Reference int& r Another name for an existing object Must be bound at creation; cannot be null
Pointer int* p An address, which may be nullptr Dereferencing nullptr or a stale address is undefined behaviour

A by-value parameter is a copy, which is why examples/module-01/02-values-references-pointers/main.cpp prints "unchanged" for the first of its three functions and "changed" for the other two. Predict that output before you run it — it is the cheapest understanding test in the module.

Undefined behaviour, in one sentence: the program may do anything, including appear to work. "It ran on my machine" is never evidence that code is correct. The course returns to this in Modules 4, 10 and 11.


Checkpoint

Answer these before moving to the worked examples. If any answer is not immediate, re-read the section named beside it.

  1. Name the four stages between saving a .cpp file and seeing output. Which stage catches a missing semicolon, and which catches a missing function definition? (Section 1 and 2.)
  2. Predict: what does this print, and why?
    #include <iostream>
    void doubleIt(int n) { n = n * 2; }
    int main() {
        int count = 5;
        doubleIt(count);
        std::cout << count << '\n';
    }
    
    (Section 8: the parameter is a copy.)
  3. A program compiles cleanly and then prints nonsense. Which of the three failure kinds is that, and what is the most likely cause? (Section 2; 04-debugging.md has the list.)
  4. Name one concrete advantage of Catalogue over the parallel arrays, and one thing that the class version still does not protect. (Sections 4 and 5 — the second half is the honest part, and Module 4 answers it.)
  5. Your friend says "object-oriented means using classes". Give one example where a class would make a program worse. (Section 6.)

Next: Worked examples — four annotated programs, each with a prediction prompt. · All modules