Interfaces and Implementations in C++
A declaration is used as an interface: it introduces a name and describes how it can be used.
Why Programs Need a Boundary
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.
As you read the sections that follow, keep one question in mind: is this code describing what something looks like to the outside world, or is it providing the working details that make it run? That question, rather than the exact punctuation, is what separates the two ideas.
What do you think happens?
Consider the line double sqrt(double); Does this line contain the computation that finds a square root?
Reveal answer
No, the line names the function and describes how to call it, but leaves out the computation
A declaration gives a return type, a name, and a parameter type, but not the computation itself. The work happens elsewhere, in the definition.
Two Faces of Every Part
A real C++ program is rarely one block of code written top to bottom. It is assembled from many separately developed parts: functions, user-defined types, class hierarchies, and templates. Each part was written by someone who had to decide how the rest of the program would use it. Keeping these parts manageable depends on being precise about how they connect.
Every part has two faces. The interface is what another piece of code must know in order to use that part. The implementation is the actual work the part does to deliver its result. A caller of a square root routine needs to know what to pass in and what comes back; it does not need every step taken to compute the answer. Thinking of the two as different things lets you reason about one without reopening the other.
The diagram shows the relationship. Code that uses a part depends only on its interface, and the interface is the agreed boundary. The implementation sits behind that boundary and can change as long as the interface stays the same.
Reading a Function Declaration
C++ has a direct way to write down an interface: the declaration. A declaration gives everything needed to use a function or a type, while leaving out the body that does the actual work. Here is the standard square root declaration:
- double (first): the return type, the kind of value the function gives back
- sqrt: the name that identifies the function
- (double): the parameter type, the kind of value a caller must supply
- ;: ends the declaration
Nothing in this line says how the square root is computed, and that omission is the point. A caller can use sqrt correctly from this line alone: it knows the name, what to pass in, and what comes back.
Declaration Versus Definition
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.
Consider a function that adds two integers. The declaration names the function add, says it takes two integers, and says it returns an integer. Any code that reads this line learns how to call add correctly. It learns nothing about the arithmetic inside, and that omission is intentional.
- The first line only introduces the name and signature
- The braces and body in the second line provide the working details
- The signature in the second line matches the first, which ties the two together
The body between the braces is the implementation, and it is what actually runs when the function is called. The signature in the definition matches the declaration, which is what links the two: the definition fulfills the interface the declaration described.
Tracing a Call Back to Its Declaration
When a result looks wrong, the declaration is the first place to check. A call must match the declaration it refers to. The declaration fixes the parameter types, so a value passed in a call is handled according to the parameter type the declaration names, and the function that runs receives it in that form.
Following a Call Through the Declaration
A program declares double sqrt(double); and a caller writes sqrt(16.0). Trace which part the call depends on and what each part contributes.
- 1Read the call site
The caller passes 16.0. It needs only the interface to know that sqrt takes one double and returns a double.
- 2Match against the declaration
The declaration sqrt(double) is the agreed boundary. The argument must match the declared parameter type, so the call is checked against that line.
- 3Locate the implementation
The definition that supplies the computation is separate. The call depends on the declaration, while the definition does the work when the call runs.
- 4Check the return type
The declaration says a double comes back, so the caller can use the result as a double without reading the body.
The caller depends only on the declaration. If a wrong value appears, compare the argument type and the return type against the declaration line first.
Classes as Interfaces
Functions are not the only thing that gets a declaration. A user-defined type, such as a class, can also be described by one. C++ supports user-defined types, and classes are the main example. Once a type like Vector is declared, the rest of the program can treat it as a new kind of value, one that it can create, index, and ask for its size.
- public section: the operations a user relies on
- private section: members that belong to the implementation side
- const after
size(): the call does not change the object
The declaration tells a user what a Vector can do without showing how it does it. The way elem and sz are arranged internally is part of the implementation, and it is the piece that can be revised while callers keep working, provided the public operations stay the same.
Mistakes When Reading Interfaces
Common mistake Treating a declaration as if it contains the working code
What it looks likeLooking at double sqrt(double); and searching it for the square root steps
Why it failsA declaration names the function and describes its use. It leaves out the computation on purpose.
Do this insteadAsk whether the line describes what can be used or provides how it works, and look for the body in the definition.
Common mistake Changing the parameter type in one place only
What it looks likeKeeping a declaration that takes an int while the definition is written to take a double
Why it failsThe definition is supposed to fulfill the interface the declaration describes. Mismatched names or types break that link.
Do this insteadKeep the declaration and definition in agreement, and check the parameter types whenever a call gives an unexpected result.
Common mistake Assuming a caller must read the implementation to use a part correctly
Practice Reading Lines
For each line, decide whether it is a declaration (interface) or a definition (implementation): (1) int add(int, int); (2) int add(int a, int b) { return a + b; } (3) class Vector { public: int size() const; }; What does a caller learn from line (1) that it does not learn from line (2)?
Hints
- Ask whether the line ends with a semicolon or has a body in braces
- A caller learns the name, parameter types, and return type from the interface line
Key Takeaways
- A declaration is used as an interface: it introduces a name and describes how it can be used, including return type, name, and parameter types.
- A definition is used as an implementation: it supplies the working details, such as the body of a function, behind the name.
- The two roles are linked by matching names and types, so a definition can fulfill the interface a declaration describes.
- Code that uses a part depends on its interface; the implementation can change as long as the interface stays the same.
- When a result looks wrong, check the call against the declaration it must match, starting with parameter and return types.