Concepts / Interfaces and Implementations in C++

Interfaces and Implementations in C++

A declaration is used as an interface: it introduces a name and describes how it can be used.

  • Programming

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?

  • Yes, the computation is written out on this line

  • No, the line names the function and describes how to call it, but leaves out the computation

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

Calling codeuses the partDeclarationinterface, the agreedboundaryDefinitionimplementation, does theworkdepends onmatched by name and types
Which part does a caller depend on: the declaration in a header, or the definition in a separate source file? The caller depends on the interface, and the implementation sits behind it.

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:

cpp
Notes on the code
  • 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
doublereturn type: value givenbacksqrtname of the function(double)parameter type: value acaller supplies;declaration ends, no body
Which parts of a declaration tell a caller how to call the function and what they will get back?

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

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.

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

Name and signaturereturn type, name,parametersName and signaturemust match the declarationNo bodyends with a semicolonBody in bracesthe statements that runDescribes usethe interfaceSupplies the workthe implementation
How does a line that only introduces a name and signature differ from a line that also provides a body, and which one does a caller depend on?

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.

Worked example

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.

  1. 1
    Read the call site

    The caller passes 16.0. It needs only the interface to know that sqrt takes one double and returns a double.

  2. 2
    Match 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.

  3. 3
    Locate 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.

  4. 4
    Check the return type

    The declaration says a double comes back, so the caller can use the result as a double without reading the body.

Result

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.

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

    Looking at double sqrt(double); and searching it for the square root steps

    Why it fails

    A declaration names the function and describes its use. It leaves out the computation on purpose.

    Do this instead

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

    Keeping a declaration that takes an int while the definition is written to take a double

    Why it fails

    The definition is supposed to fulfill the interface the declaration describes. Mismatched names or types break that link.

    Do this instead

    Keep 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

EASY

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.

Related concepts

More concepts starting with I