Organizing Code Across Multiple Files
Think about what a reader of your code knows at three moments: before anything is written about add, after its shape has been announced, and after its working details have been supplied.
Why Split a Program Into Pieces
When you write a C++ program that is split into several pieces, you constantly need to tell the compiler about things that exist somewhere else. Two ideas do most of this work: the declaration and the definition. They are easy to confuse because they often look similar on the page, yet they play different roles in a modular program.
The core distinction this article builds on is that a declaration is used as an interface, while a definition is used as an implementation. Keeping that pair of roles in mind will make it much easier to read unfamiliar code and to decide where a given piece of code belongs.
As you read the sections that follow, keep one question in mind for every line you see: does this code describe what something looks like to the outside world, or does it provide the working details that make it run? That question, rather than the exact punctuation, is what separates the two ideas.
Tracing the Two Roles Through One Name
Imagine a function named add that takes two integers and returns an integer. Think about what a reader of your code knows at three moments: before anything is written about add, after its shape has been announced, and after its working details have been supplied. The reader's knowledge grows in two distinct stages, and each stage is handled by a different kind of line.
Stage one: the name and its shape become known. The reader learns that add exists, what it accepts, and what it gives back. Stage two: the working details become known. The reader learns what add actually computes. The first stage is a declaration. The second stage is a definition. Tracing a name through these two stages is the habit this article is trying to build.
What do you think happens?
Before reading on: after the line int add(int a, int b); has been read, can a caller of add know what the function computes?
Reveal answer
No, the caller knows only the name, parameter types, and return type
The declaration is a promise about shape. It names add, says it takes two integers, and says it returns an integer. It says nothing about the arithmetic inside, and that omission is intentional.
The Declaration as an Interface
A declaration is used as an interface: it introduces a name and describes how it can be used.
A declaration is a promise about shape. Any code that reads a declaration learns how to call the named thing correctly. It learns nothing about the work happening inside, and that omission is deliberate. The declaration exposes only what a caller needs to know.
- Introduces the name add
- Says add takes two integers
- Says add returns an integer
- Ends with a semicolon and has no body, so it provides no working details
Reading this one line tells you everything you need to write a call to add: how many arguments to pass, what type each should be, and what kind of value you will get back. It tells you nothing about how the sum is computed. That is why a declaration works as an interface between the code that defines add and the code that uses it.
The Definition as an Implementation
A definition is used as an implementation: it supplies the working details behind the name.
- The first line matches the declaration above it in name, parameter types, and return type
- The body between the braces is the implementation
- The body is what actually runs when add is called
The body between the braces is the implementation, and it is what actually runs when the function is called. The signature in the first line matches the declaration, and that match is what ties the two together: the definition fulfills the interface that the declaration described.
How Matching Names and Types Link the Roles
The two roles are linked by matching names and types. A definition can fulfill the interface a declaration describes only when the two agree on what they are. Compare the declaration and the definition side by side below to see which parts must line up and which part differs.
int add(int a, int b);Declaration: the name add and its usage contract are introduced, with no working details
int add(int a, int b) {
return a + b;
}Definition: the same signature now carries a body that supplies the working details
Reading the Role of a Single Line
When you read an unfamiliar line, ask whether it describes what can be used or provides how it works. The parts of the line give you the evidence. Take the declaration of add and break it into its tokens. The return type, the name, and the parameter list announce the interface. The terminating semicolon, with no body following it, tells you that no working details are supplied on this line.
A line that ends in a semicolon after its signature, with no braces and no body, declares a name. A line whose signature is followed by a body in braces supplies the implementation for that name.
Mistakes Beginners Make With the Two Roles
Common mistake Treating the declaration as if it contains the working details
What it looks likeLooking at int add(int a, int b); and expecting to find the arithmetic
Why it failsA declaration introduces a name and describes how it can be used. It deliberately leaves out the implementation.
Do this insteadRead a declaration as a description of usage. Look for a definition with a body to find how the work is done.
Common mistake Assuming any signature that looks like a function must be a definition
What it looks likeReading int add(int a, int b); and concluding that add is implemented here
Why it failsThe semicolon with no body marks the line as a declaration, so it supplies an interface only.
Do this insteadCheck whether the signature is followed by a body in braces before deciding its role.
Common mistake Writing a definition whose name or types do not match the declaration
What it looks likeDeclaring int add(int a, int b); and then defining it as double add(double a, double b) { ... }Why it failsThe two roles are linked by matching names and types, so a mismatched definition does not fulfill the interface that was declared.
Do this insteadCopy the signature from the declaration exactly, then supply the body.
Practice Reading Declarations and Definitions
For each line below, decide whether it declares a name, defines it, or both. Explain your answer by pointing to the semicolon or the braces. Line A: int multiply(int a, int b); Line B: int multiply(int a, int b) { return a * b; }
Hints
- Ask whether the signature is followed by a semicolon or by a body in braces.
- Ask what each line tells a caller: only how to use the name, or also how the work is done.
- A line that supplies a body provides the implementation for the name it introduces.
Write one declaration and one matching definition for a function named square that takes one integer and returns an integer. Then state, in one sentence each, what a caller learns from the declaration and what the definition adds.
Hints
- Make the return type and parameter types of the definition identical to those of the declaration.
- The definition's body should compute the result that the declaration promised.
Summary of Declarations and Definitions
- 1A declaration is used as an interface: it introduces a name and describes how it can be used.
- 2A definition is used as an implementation: it supplies the working details behind the name.
- 3The two roles are linked by matching names and types, so a definition can fulfill the interface a declaration describes.
- 4When reading code, ask whether a line describes what can be used or provides how it works.
At a glance
- A declaration is used as an interface: it introduces a name and describes how it can be used.
- A definition is used as an implementation: it supplies the working details behind the name.
- Matching names and types link a definition to the declaration whose interface it fulfills.
- When reading a line, check whether it ends after its signature or is followed by a body to tell which role it plays.