Object Creation in C++
The Vector object is a single object, but it refers to a collection of values.
Why Defining a Class Is Only Half the Work
Writing a class tells the compiler what an object is. It does not, by itself, do anything useful in a program. The other half of the work is using the class: creating objects of it, asking them questions, and changing their contents. This article follows a short function called read_and_sum() that makes use of a Vector class. Its job is to read a number of values, store them, and return their total.
The function is simple enough to follow line by line, which makes it a good place to see how a program talks to an object. As you read, notice every place where the Vector object appears. Ask what the function needs from the object, and also what it never needs to know about the object. The answers to those two questions are the main lesson.
Tracing read_and_sum() Step by Step
The function is a client of the Vector class. A client is code that creates objects and uses them through the operations the class offers. Here the client does exactly three things with the Vector. It creates one with the declaration Vector v(s). It reads and writes its elements with v[i]. It asks how many elements it holds with v.size(). Those three uses are the whole of the interaction. The function never reaches inside the object to manipulate its storage directly.
Vector v(s);
for (int i = 0; i != v.size(); ++i)
cin >> v[i];
int sum = 0;
for (int i = 0; i != v.size(); ++i)
sum += v[i];
return sum;- 1Line 1 is the creation step. The Vector constructor, written here as Vector v(s), builds an object that holds s elements.
- 2Lines 2 and 3 form the reading loop.
v.size()supplies the count that controls when the loop stops, and v[i] selects one element for cin to fill. - 3Lines 5 to 7 form the summing loop. It repeats the same stopping test and the same v[i] access, so both loops depend on the same public interface.
- 4The function never names elem or sz. Everything it needs comes through the three public members.
- Vector v(s) creates the object with s elements
- i !=
v.size()is the stopping test for the reading loop - v[i] gives access to one element so cin can store a value in it
- The second loop uses the same stopping test and access form to add the elements
- The function touches the object only through
Vector(), operator[](), andsize()
The snippet above is an illustrative reconstruction that follows the pattern the source describes. It is not a verbatim copy of the original function. The important feature is the pattern itself: one creation step, then two loops that both use v.size() and v[i].
Read the sequence from top to bottom. The creation happens once, before any other use. The size() check and the v[i] accesses then repeat inside the two loops. Each call is a request sent from the function to the object, and the function does not need to know how the object carries out the request.
The Public Interface and the Hidden Representation
A class has two sides. The class interface is the set of public members that clients may use. In this example the interface is Vector(), operator[](), and size(). The hidden side is the representation, which is the way the class stores its data. In the Vector class the representation is held in two members, named elem and sz.
The diagram separates the two sides. On one side sit the three members a client may call. On the other sit elem and sz, which the source says are accessible only through that interface. The read_and_sum() function sits entirely on the interface side. It never needs to name elem or sz. That is the point of the design: the client depends on what the object does, not on how it stores its data.
Where the Elements Live and Which Indices Are Valid
The Vector object is a single object, but it refers to a collection of values. The count of those values is kept in sz, and the values themselves are reached through elem. The client never follows elem by hand. It asks for elements through v[i], and it learns the count through v.size().
In the diagram, sz records how many elements the object holds, and elem leads to the stored elements. The two fields are related: the count in sz is what size() reports, and it is the number that tells the client how far its loops should run. The exact way the storage is arranged is part of the hidden representation, so a client should rely on v[i] and v.size() rather than on that arrangement.
The index map shows the rule the loops rely on. When size() returns 4, the valid indices run from 0 to 3, which is the range 0 to sz minus 1. The stopping test i != v.size() ends the loop at exactly the point where i would reach 4, so the loop never asks for v[4]. The read and sum loops stay inside the valid range only because their stopping test is built from size().
What do you think happens?
If size() returns 4, what is the largest index you may pass to v[i] without leaving the valid range?
Reveal answer
3
Valid indices run from 0 up to size() minus 1. With four elements, the indices are 0, 1, 2, and 3.
Where a Wrong Result Usually Comes From
When a function that uses a class returns a wrong result, the fault is usually in one of the places where the client meets the object. Trace the value from input, through the Vector, into the sum. The points where that value crosses the boundary between client and object are the first places to check.
Common mistake Creating the Vector with the wrong number of elements
What it looks likeVector v(s) is written where s does not hold the count the input provided.
Why it failsThe object is created with s elements, so every later loop and every v[i] access depends on that size.
Do this insteadConfirm that the value passed to the constructor matches the number of values the function is supposed to read and store.
Common mistake Indexing outside the valid range
What it looks likeA loop runs while i is less than or equal to v.size(), so v[v.size()] is read.
Why it failsValid indices run from 0 to
size()minus 1, so the index equal tosize()is out of range.Do this insteadKeep the stopping test i != v.size() or use i < v.size(), and never index with v.size() itself.
Common mistake Reaching into elem or sz from the client
What it looks likeread_and_sum() reads a member named sz to decide where its loop stops.
Why it failsThose members are the hidden representation. Using them bypasses the interface the class is designed to offer.
Do this insteadAsk v.size() for the count and use v[i] for each element.
Common mistake Summing before every element has been stored, so the total uses stale or missing values
Common mistake Mixing up the reading loop and the summing loop when debugging, instead of checking each loop separately
When a class-using function misbehaves, separate the client code from the object. First ask whether the client is calling the public interface correctly. Check that the object is created with the right size, that each index stays within range, and that size() controls the loops. Only after those checks should you suspect the class itself.
Practice: Trace a Client Function
Suppose a function creates a Vector with 5 elements, reads values into it with a loop that runs while i is less than or equal to v.size(), and then returns the sum. Without looking at the Vector class itself, identify which line is most likely to cause a wrong result and explain how you would check the client before checking the class.
Hints
- Compare the loop's stopping test with the rule for valid indices.
- Ask whether the fault would be in the client code or in the class, and which check comes first.
- The class interface is
Vector(), operator[](), andsize(). Which of these does the loop depend on?
Key Takeaways
- The
read_and_sum()function is a client of Vector: it creates an object with Vector v(s), reads and writes elements with v[i], and asks for the count withv.size(). - The class interface is the public set of members,
Vector(), operator[](), andsize(), while the representation elem and sz is hidden behind it through encapsulation. - A client should never need elem or sz; depending only on the interface lets the internals change without changing the functions that use the class.
- Valid indices run from 0 to
size()minus 1, so loop stopping tests built fromsize()are the usual place to look first when a result is wrong. - When debugging, check the client's use of the public interface first, and only then examine the class itself.