7.1.2. Defining the Revised Sales_data Class

Image

Our 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&);


Image Note

Functions defined in the class are implicitly inline6.5.2, p. 238).


Defining Member Functions

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.

Introducing this
Image

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.

Introducing const Member Functions

The 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 const6.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.


Image Note

Objects that are const, and references or pointers to const objects, may call only const member functions.


Class Scope and 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.

Defining a Member Function outside the Class

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.

Defining a Function to Return “This” Object

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.


Exercises Section 7.1.2

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.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.249.220