Section 7.7. Class Member Functions


7.7. Class Member Functions

In Section 2.8 (p. 63) we began the definition of the Sales_item class used in solving the bookstore problem from Chapter 1. Now that we know how to define ordinary functions, we can continue to fill in our class by defining the member functions of this class.

We define member functions similarly to how we define ordinary functions. As with any function, a member function consists of four parts:

  • A return type for the function

  • The function name

  • A (possibly empty) comma-separated list of parameters

  • The function body, which is contained between a pair of curly braces

As we know, the first three of these parts constitute the function prototype. The function prototype defines all the type information related to the function: what its return type is, the function name, and what types of arguments may be passed to it. The function prototype must be defined within the class body. The body of the function, however, may be defined within the class itself or outside the class body.

With this knowledge, let's look at our expanded class definition, to which we've added two new members: the member functions avg_price and same_isbn. The avg_price function has an empty parameter list and returns a value of type double. The same_isbn function returns a bool and takes a single parameter of type reference to const Sales_item.

      class Sales_item {      public:          // operations on Sales_item objects          double avg_price() const;          bool same_isbn(const Sales_item &rhs) const               { return isbn == rhs.isbn; }      // private members as before      private:          std::string isbn;          unsigned units_sold;          double revenue;      }; 

We'll explain the meaning of the const that follows the parameter lists shortly, but first we need to explain how member functions are defined.

7.7.1. Defining the Body of a Member Function

We must declare all the members of a class within the curly braces that delimit the class definition. There is no way to subsequently add any members to the class. Members that are functions must be defined as well as declared. We can define a member function either inside or outside of the class definition. In Sales_item, we have one example of each: same_isbn is defined inside the Sales_item class, whereas avg_price is declared inside the class but defined elsewhere.

A member function that is defined inside the class is implicitly treated as an inline function (Section 7.6, p. 256).

Let's look in more detail at the definition of same_isbn:

      bool same_isbn(const Sales_item &rhs) const          { return isbn == rhs.isbn; } 

As with any function, the body of this function is a block. In this case, the block contains a single statement that returns the result of comparing the value of the isbn data members of two Sales_item objects.

The first thing to note is that the isbn member is private. Even though these members are private, there is no error.

A member function may access the private members of its class.



More interesting is understanding from which Sales_item objects does the function get the values that it compares. The function refers both to isbn and rhs.isbn. Fairly clearly, rhs.isbn uses the isbn member from the argument passed to the function. The unqualified use of isbn is more interesting. As we shall see, the unqualified isbn refers to the isbn member of the object on behalf of which the function is called.

Member Functions Have an Extra, Implicit Parameter

When we call a member function, we do so on behalf of an object. For example, when we called same_isbn in the bookstore program on page 26, we executed the same_isbn member on the object named total:

      if (total.same_isbn(trans)) 

In this call, we pass the object trans. As part of executing the call, the object trans is used to initialize the parameter rhs. Thus, in this call, rhs.isbn is a reference to trans.isbn.

The same argument-binding process is used to bind the unqualified use of isbn to the object named total. Each member function has an extra, implicit parameter that binds the function to the object on which the function was called. When we call same_isbn on the object named total, that object is also passed to the function. When same_isbn refers to isbn, it is implicitly referring to the isbn member of the object on which the function was called. The effect of this call is to compare total.isbn with TRans.isbn.

Introducing this

Each member function (except for static member functions, which we cover in Section 12.6 (p. 467)) has an extra, implicit parameter named this. When a member function is called, the this parameter is initialized with the address of the object on which the function was invoked. To understand a member function call, we might think that when we write

      total.same_isbn(trans); 

it is as if the compiler rewrites the call as

      // pseudo-code illustration of how a call to a member function is translated      Sales_item::same_isbn(&total, trans); 

In this call, the data member isbn inside same_isbn is bound to the one belonging to total.

Introducing const Member Functions

We now can understand the role of the const that follows the parameter lists in the declarations of the Sales_item member functions: That const modifies the type of the implicit this parameter. When we call total.same_isbn(trans), the implicit this parameter will be a const Sales_Item* that points to total. It is as if the body of same_isbn 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 same_isbn is a const member      bool Sales_item::same_isbn(const Sales_item *const this,                                const Sales_item &rhs) const      { return (this->isbn == rhs.isbn); } 

A function that uses const in this way is called a const member function. Because this is a pointer to const, a const member function cannot change the object on whose behalf the function is called. Thus, avg_price and same_isbn may read but not write to the data members of the objects on which they are called.

A const object or a pointer or reference to a const object may be used to call only const member functions. It is an error to try to call a nonconst member function on a const object or through a pointer or reference to a const object.



Using the this Pointer

Inside a member function, we need not explicitly use the this pointer to access the members of the object on which the function was called. Any unqualified reference to a member of our class is assumed to be a reference through this:

      bool same_isbn(const Sales_item &rhs) const          { return isbn == rhs.isbn; } 

The uses of isbn in this function are as if we had written this->units_sold or this->revenue.

The this parameter is defined implicitly, so it is unnecessary and in fact illegal to include the this pointer in the function's parameter list. However, in the body of the function we can refer to the this pointer explicitly. It is legal, although unnecessary, to define same_isbn as follows:

      bool same_isbn(const Sales_item &rhs) const          { return this->isbn == rhs.isbn; } 

7.7.2. Defining a Member Function Outside the Class

Member functions defined outside the class definition must indicate that they are members of the class:

      double Sales_item::avg_price() const      {          if (units_sold)              return revenue/units_sold;          else              return 0;      } 

This definition is like the other functions we've seen: It has a return type of double and an empty parameter list enclosed in parentheses after the function name. What is new is the const following the parameter list and the form of the function name. The function name

      Sales_item::avg_price 

uses the scope operator (Section 1.2.2, p. 8) to say that we are defining the function named avg_price that is defined in the scope of the Sales_item class.

The const that follows the parameter list reflects the way we declared the member funcion inside the Sales_item header. In any definition, the return type and parameter list must match the declaration, if any, of the function. In the case of a member function, the declaration is as it appears in the class definition. If the function is declared to be a const member function, then the const after the parameter list must be included in the definition as well.

We can now fully understand the first line of this code: It says we are defining the avg_price function from the Sales_item class and that the function is a const member. The function takes no (explicit) parameters and returns a double.

The body of the function is easier to understand: It tests whether units_sold is nonzero and, if so, returns the result of dividing revenue by units_sold. If units_sold is zero, we can't safely do the divisiondividing by zero has undefined behavior. In this program, we return 0, indicating that if there were no sales the average price would be zero. Depending on the sophistication of our error-handling strategy, we might instead throw an exception (Section 6.13, p. 215).

7.7.3. Writing the Sales_item Constructor

There's one more member that we need to write: a constructor. As we learned in Section 2.8 (p. 65), class data members are not initialized when the class is defined. Instead, data members are initialized through a constructor.

Constructors Are Special Member Functions

A constructor is a special member function that is distinguished from other member functions by having the same name as its class. Unlike other member functions, constructors have no return type. Like other member functions they take a (possibly empty) parameter list and have a function body. A class can have multiple constructors. Each constructor must differ from the others in the number or types of its parameters.

The constructor's parameters specify the initializers that may be used when creating objects of the class type. Ordinarily these initializers are used to initialize the data members of the newly created object. Constructors usually should ensure that every data member is initialized.

The Sales_item class needs to explicitly define only one constructor, the default constructor, which is the one that takes no arguments. The default constructor says what happens when we define an object but do not supply an (explicit) initializer:

      vector<int> vi;       // default constructor: empty vector      string s;             // default constructor: empty string      Sales_item item;      // default constructor: ??? 

We know the behavior of the string and vector default constructors: Each of these constructors initializes the object to a sensible default state. The default string constructor generates an empty string, the one that is equal to "". The default vector constructor generates a vector with no elements.

Similarly, we'd like the default constructor for Sales_items to generate an empty Sales_item. Here "empty" means an object in which the isbn is the empty string and the units_sold and revenue members are initialized to zero.

Defining a Constructor

Like any other member function, a constructor is declared inside the class and may be defined there or outside the class. Our constructor is simple, so we will define it inside the class body:

      class Sales_item {      public:          // operations on Sales_item objects          double avg_price() const;          bool same_isbn(const Sales_item &rhs) const              { return isbn == rhs.isbn; }          // default constructor needed to initialize members of built-in type          Sales_item(): units_sold(0), revenue(0.0) { }      // private members as before      private:          std::string isbn;          unsigned units_sold;          double revenue;      }; 

Before we explain the constructor definition, note that we put the constructor in the public section of the class. Ordinarily, and certainly in this case, we want the constructor(s) to be part of the interface to the class. After all, we want code that uses the Sales_item type to be able to define and initialize Sales_item objects. Had we made the constructor private, it would not be possible to define Sales_item objects, which would make the class pretty useless.

As to the definition itself

      // default constructor needed to initialize members of built-in type      Sales_item(): units_sold(0), revenue(0.0) { } 

it says that we are defining a constructor for the Sales_item class that has an empty parameter list and an empty function body. The interesting part is the colon and the code between it and the curly braces that define the (empty) function body.

Constructor Initialization List

The colon and the following text up to the open curly is the constructor initializer list. A constructor initializer list specifies initial values for one or more data members of the class. It follows the constructor parameter list and begins with a colon. The constructor initializer is a list of member names, each of which is followed by that member's initial value in parentheses. Multiple member initializations are separated by commas.

This initializer list says that both the units_sold and revenue members should be initialized to 0. Whenever a Sales_item object is created, these members will start out as 0. We need not specify an initial value for the isbn member. Unless we say otherwise in the constructor initializer list, members that are of class type are automatically initialized by that class' default constructor. Hence, isbn is initialized by the string default constructor, meaning that isbn initially is the empty string. Had we needed to, we could have specified a default value for isbn in the initializer list as well.

Having explained the initializer list, we can now understand the constructor: Its parameter list and the function body are both empty. The parameter list is empty because we are defining the constructor that is run by default, when no initializer is present. The body is empty because there is no work to do other than initializing units_sold and revenue. The initializer list explicitly initializes units_sold and revenue to zero and implicitly initializes isbn to the empty string. Whenever we create a Sales_item object, the data members will start out with these values.

Synthesized Default Constructor

If we do not explicitly define any constructors, then the compiler will generate the default constructor for us.



The compiler-created default constructor is known as a synthesized default constructor. It initializes each member using the same rules as are applied for variable initializations (Section 2.3.4, p. 50). Members that are of class type, such as isbn, are initialized by using the default constructor of the member's own class. The initial value of members of built-in type depend on how the object is defined. If the object is defined at global scope (outside any function) or is a local static object, then these members will be initialized to 0. If the object is defined at local scope, these members are uninitialized. As usual, using an uninitialized member for any purpose other than giving it a value is undefined.

The synthesized default constructor often suffices for classes that contain only members of class type. Classes with members of built-in or compound type should usually define their own default constructors to initialize those members.



Because the synthesized constructor does not automatically initialize members of built-in type, we had to define the Sales_item default constructor explicitly.

7.7.4. Organizing Class Code Files

As we saw in Section 2.9 (p. 67), class declarations ordinarily are placed in headers. Usually, member functions defined outside the class are put in ordinary source files. C++ programmers tend to use a simple naming convention for headers and the associated class definition code. The class definition is put in a file named type .h or type .H, where type is the name of the class defined in the file. Member function definitions usually are stored in a source file whose name is the name of the class. Following this convention we put the Sales_item class definition in a file named Sales_item.h. Any program that wants to use the class must include that header. We should put the definition of our Sales_item functions in a file named Sales_item.cc. That file, like any other file that uses the Sales_item type, would include the Sales_item.h header.

Exercises Section 7.7.4

Exercise 7.31:

Write your own version of the Sales_item class, adding two new public members to read and write Sales_item objects. These functions should operate similarly to the input and output operators used in Chapter 1. Transactions should look like the ones defined in that chapter as well. Use this class to read and write a set of transactions.

Exercise 7.32:

Write a header file to contain your version of the Sales_item class. Use ordinary C++ conventions to name the header and any associated file needed to hold non-inline functions defined outside the class.

Exercise 7.33:

Add a member that adds two Sales_items. Use the revised class to reimplement your solution to the average price problem presented in Chapter 1.




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net