Concepts / Organizing Code Across Multiple Files

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.

  • Programming

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?

  • Yes, the caller knows the arithmetic because the parameters are named

  • No, the caller knows only the name, parameter types, and return type

Reveal answer
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

Definition

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.

cpp
Notes on the code
  • 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

Definition

A definition is used as an implementation: it supplies the working details behind the name.

cpp
Notes on the code
  • 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.

Before
int add(int a, int b);

Declaration: the name add and its usage contract are introduced, with no working details

After
int add(int a, int b) {
    return a + b;
}

Definition: the same signature now carries a body that supplies the working details

DeclarationDefinitionInterfaceroleImplementationroleName and usagecontractwhat it providesWorking detailswhat it providesSignature ending in asemicoloncode formSignature followed bya bodycode formHow to call addwhat the reader learnsHow add computes itsresultwhat the reader learns
What each role provides, and what a reader of the code learns from it

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.

intreturn type: what thecaller gets backaddname being introduced(int a, int b)parameters: what the callermust pass;no body follows, so this isa declaration
Which parts of this line announce the interface and which show that no implementation is present
Key point

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 like

    Looking at int add(int a, int b); and expecting to find the arithmetic

    Why it fails

    A declaration introduces a name and describes how it can be used. It deliberately leaves out the implementation.

    Do this instead

    Read 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 like

    Reading int add(int a, int b); and concluding that add is implemented here

    Why it fails

    The semicolon with no body marks the line as a declaration, so it supplies an interface only.

    Do this instead

    Check 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 like
    Declaring int add(int a, int b); and then defining it as double add(double a, double b) { ... }
    Why it fails

    The two roles are linked by matching names and types, so a mismatched definition does not fulfill the interface that was declared.

    Do this instead

    Copy the signature from the declaration exactly, then supply the body.

Practice Reading Declarations and Definitions

EASY

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.
MEDIUM

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

  1. 1
    A declaration is used as an interface: it introduces a name and describes how it can be used.
  2. 2
    A definition is used as an implementation: it supplies the working details behind the name.
  3. 3
    The two roles are linked by matching names and types, so a definition can fulfill the interface a declaration describes.
  4. 4
    When 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.

Related concepts

More concepts starting with O