Concepts / Interfaces and Implementations of Classes

Interfaces and Implementations of Classes

Separate compilation divides code into files that are compiled independently, so user code can depend on an interface rather than an implementation.

  • Programming

A File That Uses a Class It Did Not Write

Large programs are rarely written as one file. Separate compilation divides code into files that are compiled independently, and this division changes what a file has to know. A file that uses a class does not have to contain that class's implementation. It can depend on an interface instead. This article follows one user-side file from start to finish and asks, for every name it uses, which line brings that name into the file.

What do you think happens?

To be written and compiled, does user.cpp need the code that implements the Vector class, or does it need something smaller?

  • It needs the full implementation of Vector

  • It needs only the interface of Vector

Reveal answer
Answer:

It needs only the interface of Vector

The user file can be written, read, and compiled on its own. Its author depends on what the interface promises, not on the details inside the implementation.

Tracing the Three Sources of Names

The example file depends on three different sources of names: a file written by someone else (Vector.h), a standard library header (cmath), and the std namespace that the standard library uses. Before reading the code, look at what the function needs. The function sqrt_sum must know what a Vector is and how to ask it for its size and its elements, and it also calls sqrt. Each of those needs is supplied by one of the three include and using lines at the top of the file.

cpp
Notes on the code
  • The first line gives user code access to the Vector interface
  • The second line brings in the declarations for standard math functions such as sqrt
  • The third line makes standard library members visible without a prefix
  • The loop condition i != v.size() uses the interface of Vector
  • The statement sum += sqrt(v[i]) adds the square root of each element to a running total
Formula

In the formula, $v_i$ stands for the element at index $i$ of the vector. The loop body computes the square root of each element and adds it to the running total, so the function returns the sum of those square roots. The function body combines three kinds of names: the size and indexing operations come from Vector.h, and sqrt comes from cmath. Each one traces back to a specific line at the top of the file.

user.cpp#include Vector.hinterface of VectorVector#include cmathdeclarations of mathfunctionssize() and v[i]using namespace stdstd members visible withoutprefixsqrtintroducesintroducesdeclarespermits plain name
Which include or using line introduces each name the function needs into the user file?

Vector.h Supplies the Interface

The first line of user.cpp, #include "Vector.h", is the link between the user file and the class. The source comment states its purpose: it gets Vector's interface. The interface is the part of the class that other code is allowed to see and use. It tells the compiler what a Vector is and what operations exist on it, such as the size() call and the indexing operation v[i] used inside the loop. The user file does not need to see how those operations are carried out.

size() declaredsize() carried outindexing v[i]declaredindexing carried outrealized inrealized in
What user code can see in the header compared with what exists only in the implementation file, and which side user code can rely on.

Why Separate Compilation Helps

Because the user file depends only on the interface, it can be written, read, and compiled on its own. The class's implementation lives in a separate file that is compiled independently. The diagram below follows the path from source files to a final executable. The user file and the implementation file each go through their own compilation step, and the class is connected to the user code at the later linking step, where the separately compiled parts are combined.

user.cppuses Vector and sqrtcompile user.cpplinkcombine separately compiledpartsexecutableVector.hinterfacecompile Vector.cppVector.cppimplementationinterface used
Which files are compiled separately, and at which step is user code connected to the class?

What the using namespace std Directive Controls

The line using namespace std; is a directive that makes the members of the std namespace visible in the file. Without this line, a standard library name such as sqrt would not be visible under its plain name. The same function can still be reached by writing the qualified name std::sqrt, which makes the namespace explicit at each use. The source points to a later section of the text for more detail on this directive.

sqrtnot visible under plainnamesqrtvisible through usingnamespace stdstd::sqrtqualified name reaches itstd::sqrtstill works
How the name sqrt resolves without and with the using directive, and what the qualified name std::sqrt does in each case.
cpp
Notes on the code
  • The include brings in the declaration of sqrt
  • The qualified name std::sqrt works without any using directive

Mistakes When Tracing Names

  • Common mistake

    Assuming the using namespace std; line is what brings sqrt into scope

    Why it fails

    The cmath include supplies the declarations for sqrt. The using directive only controls whether the plain name can be written without the std prefix.

    Do this instead

    Find the include line for the declaration, then separately check whether the using directive lets you write the plain name.

  • Common mistake

    Looking for the body of Vector's operations in user.cpp

    Why it fails

    The user file depends on the interface. The way size() and indexing are carried out lives in the implementation file.

    Do this instead

    Trace names to Vector.h for what exists, and to Vector.cpp only when you need to know how an operation is carried out.

  • Common mistake

    Believing that the user file must be compiled together with the implementation before it can be checked

Practice Tracing Names in user.cpp

EASY

Using the user.cpp example, name the line that introduces the name Vector into the user file, the line that introduces the name sqrt, and the line that makes the plain name sqrt writable without a prefix. Then say whether the user file needs the implementation of Vector in order to compile.

Hints
  • Look at the three lines at the top of the file, before the function begins
  • The name Vector comes from the interface file, and the declaration of sqrt comes from the math header
  • The using directive is the only one of the three lines that concerns the std prefix

Key Takeaways

  • Separate compilation divides code into files that are compiled independently, so user code can depend on an interface rather than an implementation.
  • The line #include "Vector.h" gives user code access to Vector's interface, which lists what operations exist without showing how they are carried out.
  • The line #include <cmath> provides the declarations for standard math functions such as sqrt(), while using namespace std; controls whether std members can be written without the std prefix.
  • To trace a name, find the include or using line that introduces it: Vector and its operations come from Vector.h, sqrt from cmath, and the plain spelling of sqrt depends on the using directive.

Related concepts

More concepts starting with I