Concepts / Classes and Member Functions

Classes and Member Functions

A class is commonly split into a header file that declares its interface and a source file that implements its member functions.

  • Programming

A Small Vector Class to Predict

Before any explanation, look at the outline of a small Vector class. Its interface lives in one file and its implementation lives in another. The implementation file is where the real work of each member function is written, and it is the piece that makes the class usable. Your job in this article is to follow where each piece of code lives and what happens when an object is created.

As you read the code, pay attention to three things: which file each function body sits in, how the constructor sets up its members, and what the bracketed index operator returns. You do not need to understand every symbol yet. The rest of the article explains each one.

Two Files for One Class

A class in C++ is usually split into two parts. The header file, which ends in .h, states what the class looks like from the outside: its name, its data, and the functions it offers. The source file, which ends in .cpp, provides the bodies of those functions. A helpful way to hold this in mind is that the header describes the promise and the source file keeps the promise.

Definition

Class declaration (header): the part that names the class, lists its data members, and declares its member functions, without giving their bodies. Member function definition (source file): the part that provides the body of a function the header declared, qualified by the class name so the compiler knows which class it belongs to.

Vector.hinterface: name, data,functionsclass declarationwhat the class looks likeconsistency checkboth checked against thesame declarationsVector.cppimplementation: functionbodiesmember functiondefinitionsthe work each function doesdeclaresdefinesincludesmust matchmust match
How does the class declaration in the header reach the source file, and why must the compiler see the declaration before it can accept the member function definitions in the .cpp file?

Why the Source File Includes the Header

The reason for the link between the two files is stated directly in the source material. To help the compiler ensure consistency, the .cpp file that provides the implementation of Vector also includes the .h file that provides its interface. The implementation file does not restate the class from memory. It pulls in the interface, so that both files are checked against the same declarations.

cpp
cpp

Read the two files together. The header promises a constructor, a size function, and a bracket operator with the matching signatures. The source file's definitions must agree with those declarations. Because the .cpp file includes the .h file, a mismatch between the promise and the implementation gives the compiler a way to notice the disagreement.

Reading a Member Function Definition

Take the bracket operator definition from the source file, double& Vector::operator[](int i), and read it from left to right. Each part answers a question the compiler needs answered.

double&return type: a reference toa doubleVectorclass name: which classowns the function::scope operator: ties thename to the classoperator[]function name: the bracketoperator(int i)parameter list: one intnamed i
Which parts of double& Vector::operator[](int i) are the return type, the class name, the scope operator, and the parameter list, and what does each one tell the compiler?

The class name and the scope operator together say that this function is a member of Vector, not a free-standing function. That is why the same name, such as size, could appear in another class without conflict. The parameter list tells the compiler what arguments a call must supply, here a single int.

Constructor Initializers Run First

A constructor is the function that sets up a new object. In the Vector source, the constructor header uses a colon after its parameter list. The colon introduces a list of member initializers, each written as a member name followed by a value in braces. The braces after the colon are not the function body. They belong to the initializer list, which is why the constructor's own body can be empty.

The source material states an important fact about this construction: class members are initialized in the constructor, and members are initialized before the constructor body is executed. This means that by the time any statement inside the braces runs, elem and sz already hold their values. The body is the last step, not the place where the members first receive values.

Initializer listelem{...}, sz{s}Members elem and szboth hold their valuesConstructor bodyruns last1. initialize2. body starts with members ready
In what order do the member initializer list, the member subobjects, and the constructor body execute, and what state does each member have when the body starts?
Worked example

Tracing Vector v(3)

Trace what happens when the program creates a Vector with s equal to 3, using the constructor in Vector.cpp.

  1. 1
    Call the constructor

    The argument 3 is bound to the parameter s.

  2. 2
    Run the initializer list

    elem is set to a new array of 3 doubles, and sz is set to s, which is 3.

  3. 3
    Check the state at the body

    When the braces are reached, elem and sz already hold their values, so the body could safely use sz.

  4. 4
    Run the body

    The body is empty in this example, so the constructor finishes with the members already set up.

Result

The new object has sz equal to 3 and elem pointing to storage for 3 doubles before any body statement runs.

Overloaded Operators as Member Functions

Operators can be overloaded to provide class-specific behavior. An overloaded operator is written as a member function whose name is the keyword operator followed by the operator symbol. In the Vector source, the bracket operator is defined in the implementation file, just like the constructor and the size function, and it is scoped to its class as Vector::operator[].

When you write an expression such as v[2], the compiler treats it as a call to the member function Vector::operator[] on the object v, with 2 as the argument. The same idea applies to other operators. For an expression a + b where + is overloaded as a member function, the left operand a plays the role of this inside the function, and b is passed in as the parameter.

aleft operand, becomes thisoperator+member function of the leftobject's classbright operand, becomes theparametercalls onpassed as argument
How does an expression like a + b map onto a call to the member function operator+, and which object becomes this and which becomes the parameter?

Mistakes With Split Class Files

  • Common mistake

    Treating the braces after the constructor colon as the function body

    What it looks like
    Vector::Vector(int s) : elem{new double[s]}, sz{s} { }
    Why it fails

    The braces that follow the colon belong to the initializer list. The body is the braces after the initializer list.

    Do this instead

    Read the colon as the start of the initializer list, then identify the last braces as the body.

  • Common mistake

    Assuming members get their values inside the constructor body

    What it looks like
    Vector::Vector(int s) : elem{nullptr} { sz = s; }
    Why it fails

    Members are initialized before the body runs. Assigning them inside the body is a different step from initializing them in the list.

    Do this instead

    Put member setup in the initializer list so the members hold their values before any body statement runs.

  • Common mistake

    Dropping the class name and scope operator in a source-file definition

    What it looks like
    int size() { return sz; }
    Why it fails

    Without Vector:: the definition does not say which class the function belongs to, so it does not match the declaration in the header.

    Do this instead

    Write int Vector::size() { return sz; } so the compiler knows which class declared it.

  • Common mistake

    Forgetting to include the header in the implementation file

    Why it fails

    The source file then does not see the class declaration it is supposed to implement, so the consistency check between the two files cannot happen.

    Do this instead

    Start the .cpp file with the include of the matching .h file.

Trace Your Own Vector

EASY

Using the Vector.h and Vector.cpp code above, suppose a program creates Vector w(5) and then reads w[1]. Name the file that contains the constructor body, state what sz holds when the body starts, and identify the class name and scope operator in the definition that handles w[1].

Hints
  • Look at which file defines Vector::Vector.
  • Remember that members are set in the initializer list before the body runs.
  • The bracket operator is defined as Vector::operator[] in the source file.

Key Takeaways

  • A class is commonly split into a header file that declares its interface and a source file that implements its member functions, and the source file includes the header so both are checked against the same declarations.
  • A member function definition in a source file names its class with the scope operator, as in Vector::operator[], and lists its parameters.
  • Constructor member initializers, written after a colon, set members before the constructor body runs, so the body starts with members already holding their values.
  • An overloaded operator is written as a member function named operator followed by the symbol, so an expression like a + b calls a member function on the left operand.

Related concepts

More concepts starting with C