Sales_data
ClassOur revised class will have the same data members as the version we defined in § 2.6.1 (p. 72): bookNo
, a string
representing the ISBN; units_sold
, an unsigned
that says how many copies of the book were sold; and revenue
, a double
representing the total revenue for those sales.
As we’ve seen, our class will also have two member functions, combine
and isbn
. In addition, we’ll give Sales_data
another member function to return the average price at which the books were sold. This function, which we’ll name avg_price
, isn’t intended for general use. It will be part of the implementation, not part of the interface.
We define (§ 6.1, p. 202) and declare (§ 6.1.2, p. 206) member functions similarly to ordinary functions. Member functions must be declared inside the class. Member functions may be defined inside the class itself or outside the class body. Nonmember functions that are part of the interface, such as add
, read
, and print
, are declared and defined outside the class.
With this knowledge, we’re ready to write our revised version of Sales_data
:
struct Sales_data {
// new members: operations on Sales_data objects
std::string isbn() const { return bookNo; }
Sales_data& combine(const Sales_data&);
double avg_price() const;
// data members are unchanged from § 2.6.1 (p. 72)
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// nonmember Sales_data interface functions
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
Although every member must be declared inside its class, we can define a member function’s body either inside or outside of the class body. In Sales_data, isbn
is defined inside the class; combine
and avg_price
will be defined elsewhere.
We’ll start by explaining the isbn
function, which returns a string
and has an empty parameter list:
std::string isbn() const { return bookNo; }
As with any function, the body of a member function is a block. In this case, the block contains a single return
statement that returns the bookNo
data member of a Sales_data
object. The interesting thing about this function is how it gets the object from which to fetch the bookNo
member.
this
Let’s look again at a call to the isbn
member function:
total.isbn()
Here we use the dot operator (§ 4.6, p. 150) to fetch the isbn
member of the object named total
, which we then call.
With one exception that we’ll cover in § 7.6 (p. 300), when we call a member function we do so on behalf of an object. When isbn
refers to members of Sales_data
(e.g., bookNo
), it is referring implicitly to the members of the object on which the function was called. In this call, when isbn
returns bookNo
, it is implicitly returning total.bookNo
.
Member functions access the object on which they were called through an extra, implicit parameter named this
. When we call a member function, this
is initialized with the address of the object on which the function was invoked. For example, when we call
total.isbn()
the compiler passes the address of total
to the implicit this
parameter in isbn
. It is as if the compiler rewrites this call as
// pseudo-code illustration of how a call to a member function is translated
Sales_data::isbn(&total)
which calls the isbn
member of Sales_data
passing the address of total
.
Inside a member function, we can refer directly to the members of the object on which the function was called. We do not have to use a member access operator to use the members of the object to which this
points. Any direct use of a member of the class is assumed to be an implicit reference through this
. That is, when isbn
uses bookNo
, it is implicitly using the member to which this
points. It is as if we had written this->bookNo
.
The this
parameter is defined for us implicitly. Indeed, it is illegal for us to define a parameter or variable named this
. Inside the body of a member function, we can use this
. It would be legal, although unnecessary, to define isbn
as
std::string isbn() const { return this->bookNo; }
Because this
is intended to always refer to “this” object, this
is a const
pointer (§ 2.4.2, p. 62). We cannot change the address that this
holds.
const
Member FunctionsThe other important part about the isbn
function is the keyword const
that follows the parameter list. The purpose of that const
is to modify the type of the implicit this
pointer.
By default, the type of this
is a const
pointer to the nonconst
version of the class type. For example, by default, the type of this
in a Sales_data
member function is Sales_data *const
. Although this
is implicit, it follows the normal initialization rules, which means that (by default) we cannot bind this
to a const
object (§ 2.4.2, p. 62). This fact, in turn, means that we cannot call an ordinary member function on a const
object.
If isbn
were an ordinary function and if this
were an ordinary pointer parameter, we would declare this
as const Sales_data *const
. After all, the body of isbn
doesn’t change the object to which this
points, so our function would be more flexible if this
were a pointer to const
(§ 6.2.3, p. 213).
However, this
is implicit and does not appear in the parameter list. There is no place to indicate that this
should be a pointer to const
. The language resolves this problem by letting us put const
after the parameter list of a member function. A const
following the parameter list indicates that this
is a pointer to const
. Member functions that use const
in this way are const
member functions.
We can think of the body of isbn
as if it were written as
// pseudo-code illustration of how the implicit this pointer is used
// this code is illegal: we may not explicitly define the this pointer ourselves
// note that this is a pointer to const because isbn is a const member
std::string Sales_data::isbn(const Sales_data *const this)
{ return this->isbn; }
The fact that this
is a pointer to const
means that const
member functions cannot change the object on which they are called. Thus, isbn
may read but not write to the data members of the objects on which it is called.
Objects that are const
, and references or pointers to const
objects, may call only const
member functions.
Recall that a class is itself a scope (§ 2.6.1, p. 72). The definitions of the member functions of a class are nested inside the scope of the class itself. Hence, isbn
’s use of the name bookNo
is resolved as the data member defined inside Sales_data
.
It is worth noting that isbn
can use bookNo
even though bookNo
is defined after isbn
. As we’ll see in § 7.4.1 (p. 283), the compiler processes classes in two steps—the member declarations are compiled first, after which the member function bodies, if any, are processed. Thus, member function bodies may use other members of their class regardless of where in the class those members appear.
As with any other function, when we define a member function outside the class body, the member’s definition must match its declaration. That is, the return type, parameter list, and name must match the declaration in the class body. If the member was declared as a const
member function, then the definition must also specify const
after the parameter list. The name of a member defined outside the class must include the name of the class of which it is a member:
double Sales_data::avg_price() const {
if (units_sold)
return revenue/units_sold;
else
return 0;
}
The function name, Sales_data::avg_price
, uses the scope operator (§ 1.2, p. 8) to say that we are defining the function named avg_price
that is declared in the scope of the Sales_data
class. Once the compiler sees the function name, the rest of the code is interpreted as being inside the scope of the class. Thus, when avg_price
refers to revenue
and units_sold
, it is implicitly referring to the members of Sales_data
.
The combine
function is intended to act like the compound assignment operator, +=
. The object on which this function is called represents the left-hand operand of the assignment. The right-hand operand is passed as an explicit argument:
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
units_sold += rhs.units_sold; // add the members of rhs into
revenue += rhs.revenue; // the members of ''this'' object
return *this; // return the object on which the function was called
}
When our transaction-processing program calls
total.combine(trans); // update the running total
the address of total
is bound to the implicit this
parameter and rhs
is bound to trans
. Thus, when combine
executes
units_sold += rhs.units_sold; // add the members of rhs into
the effect is to add total.units_sold
and trans.units_sold
, storing the result back into total.units_sold
.
The interesting part about this function is its return type and the return
statement. Ordinarily, when we define a function that operates like a built-in operator, our function should mimic the behavior of that operator. The built-in assignment operators return their left-hand operand as an lvalue (§ 4.4, p. 144). To return an lvalue, our combine
function must return a reference (§ 6.3.2, p. 226). Because the left-hand operand is a Sales_data
object, the return type is Sales_data&
.
As we’ve seen, we do not need to use the implicit this
pointer to access the members of the object on which a member function is executing. However, we do need to use this
to access the object as a whole:
return *this; // return the object on which the function was called
Here the return
statement dereferences this
to obtain the object on which the function is executing. That is, for the call above, we return a reference to total
.
Exercise 7.2: Add the combine
and isbn
members to the Sales_data
class you wrote for the exercises in § 2.6.2 (p. 76).
Exercise 7.3: Revise your transaction-processing program from § 7.1.1 (p. 256) to use these members.
Exercise 7.4: Write a class named Person
that represents the name and address of a person. Use a string
to hold each of these elements. Subsequent exercises will incrementally add features to this class.
Exercise 7.5: Provide operations in your Person
class to return the name and address. Should these functions be const
? Explain your choice.