Chapter 21. Advanced Classes

The ruling ideas of each age have ever been the ideas of its ruling class.

Karl Marx, Manifesto of the Communist Party

This chapter discusses derived classes, virtual functions, and virtual classes.

Derived Classes

Suppose we want a stack that allows us to push on three items at a time in addition to performing all usual operations of a stack.[1] If we parse this statement in C++ terms, we discover something significant. We want a stack that:

  1. Does all the operations of a typical stack. (In C++ this is called a base class.)

  2. Expands on this by allowing us to do something more: specifically, push things on in groups of threes. (C++ calls this a derived class.)

Our basic stack is defined in Example 13-2.

We need to define a new expanded stack, which allows us to push multiple items. We call this an m_stack. This new stack does everything a simple stack does but also lets you push three items on at once. C++ allows you to build new classes on old ones. In this case we will be building our multiple-push stack (m_stack) on the existing simple stack (stack). Technically we will be using the class stack as a base class to create a new derived class, the multiple-push stack.

We start by telling C++ that we are creating m_stack out of stack:

class m_stack: public stack {

The keyword public tells C++ to make all the public members of stack accessible to the outside world. If we declared stack as private, the public and protected members of stack would be accessible only inside m_stack.

This declaration tells C++ that we are going to use stack as a base for m_stack. Figure 21-1 shows how C++ views this combination.

Derived class m_stack and base class stack
Figure 21-1. Derived class m_stack and base class stack

Now we need to define the member function that pushes three items on the stack (push_three). The code for this function looks like:

inline void m_stack::push_three(
    const int item1,
    const int item3,
    const int item3)
{
    // This calls push in the stack class
    push(item1);
    push(item2);
    push(item3);
}

We have been very careful in selecting the name of this member function. It is called push_three instead of push for a reason. If we called it push, the code:

inline void m_stack::push(
    const int item1,
    const int item3,
    const int item3)
{
    // This calls push in the m_stack class
    push(item1);
    push(item2);
    push(item3);
}

would call the member function push in the class m_stack, not stack’s push as we want. The result is that we call m_stack’s push, which calls push three times. This push belongs to m_stack, so we call push again, and so on. The result is that push will call itself over and over until the system runs out of memory.

This is not want we want. We need to tell C++ that we want to call the push in stack. This can be accomplished by using the scope operator :: . The new version of m_stack::push looks like this:

inline void m_stack::push(
    const int item1,
    const int item3,
    const int item3)
{
    // This calls push in the m_stack class
    stck::push(item1);
    stck::push(item2);
    stck::push(item3);
}

This code assumes that we need to use the name push for both the stack and m_stack classes. We don’t: the name push_three is more descriptive for the m_stack member function, so we’ll use that. The full definition for both the stack and m_stack classes is shown in Example 21-1.

Example 21-1. stack_c/stack_d1.cpp
/********************************************************
 * Stack                                                *
 *      A file implementing a simple stack class        *
 ********************************************************/
#include <cstdlib>
#include <iostream>

const int STACK_SIZE = 100;     // Maximum size of a stack

/********************************************************
 * Stack class                                          *
 *                                                      *
 * Member functions                                     *
 *      stack -- initialize the stack.                  *
 *      push -- put an item on the stack.               *
 *      pop -- remove an item from the stack.           *
 ********************************************************/
// The stack itself
class stack {
    protected:
        int count;              // Number of items in the stack
        int data[STACK_SIZE];   // The items themselves
    public:
        // Initialize the stack
        stack(  );
        // ~stack(  ) -- default destructor
        // copy constructor defaults

        // Push an item on the stack
        void push(const int item);

        // Pop an item from the stack
        int pop(  );
};

/********************************************************
 * stack::stack -- initialize the stack.                *       
 ********************************************************/
inline stack::stack(  )
{
    count = 0;  // Zero the stack
}
/********************************************************
 * stack::push -- push an item on the stack.            *
 *                                                      *
 * Warning: We do not check for overflow.               *
 *                                                      *
 * Parameters                                           *
 *      item -- item to put in the stack                *
 ********************************************************/
inline void stack::push(const int item)
{
    assert((count >= 0) &&
           (count < sizeof(data)/sizeof(data[0])));

    data[count] = item;
    ++count;
}
/********************************************************
 * stack::pop -- get an item off the stack.             *
 *                                                      *
 * Warning: We do not check for stack underflow.        *
 *                                                      *
 * Returns                                              *
 *      The top item from the stack.                    *
 ********************************************************/
inline int stack::pop(  )
{
    // Stack goes down by one
    --count;

    assert((count >= 0) &&
           (count < sizeof(data)/sizeof(data[0])));

    // Then we return the top value
    return (data[count]);
}

/********************************************************
 * m_stack -- Stack on which we can push multiple items *
 *                                                      *
 * Member function                                      *
 *      push_many -- push an item on the stack          *
 ********************************************************/
class m_stack: public stack {
    public:
        // m_stack -- default constructor
        // ~m_stack -- default destructor
        // copy constructor defaults

        // Push three items on the stack
        void push_three(const int item1,
                  const int item2,
                  const int item3);

        // Sum the elements
        int sum(  );
};
/********************************************************
 * m_stack::push_three -- push an item on the stack.    *
 *                                                      *
 * Parameters                                           *
 *      item1, item2, item3 --                          *
 *              items to put in the stack               *
 ********************************************************/
inline void m_stack::push_three(const int item1, 
                const int item2, const int item3)
{
    stack::push(item1);
    stack::push(item2);
    stack::push(item3);
}
/********************************************************
 * m_stack::sum -- Sum the elements in the stack        *
 *                                                      *
 * Returns:                                             *
 *      The elements in the stack.                      *
 ********************************************************/
inline int m_stack::sum(  ) {
    int index;          // Index into the array
    int total = 0;      // Running sum

    for (index = 0; index < count; ++index) {
        assert(index >= 0);
        assert(index < sizeof(data)/sizeof(data[0]));

        total += data[index];
    }
    return (total);
}

You may have noticed that we’ve added a member function called sum. This function returns the total of all the elements in the stack. Our sum function needs access to the array named data in the class stack to work. Normally this variable would be declared private to prevent outsiders from messing with it. But in this case we would like for no one but m_stack to be able to access this variable. The C++ keyword protected gives us the access we want. It tells C++ that any derived class that uses this class as a base class can access this data, but outsiders are locked out.

So the three protection keywords are:

private

Access is limited to the class only.

protected

The class and any derived class that use the class as a base class can access the member.

public

Anyone can access the member.

Also, because m_stack is derived from stack, you can use an m_stack type variable wherever a stack type variable is used. In the following example, we create an m_stack named multi_stack that is used as a parameter to the function push_things, which takes a normal, unbounded stack as a parameter:

void push_things(stack& a_stack) {
    a_stack.push(1);
    a_stack.push(2);
}

// ...
m_stack multi_stack; // A random stack
// ....
push_things(bounded_stack);

The function push_things takes a stack as a parameter. Even though the variable multi_stack is an m_stack type variable, C++ turns it into a stack when push_things is called.

One way to explain this is that although multi_stack is of type m_stack, when it is used by push_things, the function is looking through a peephole that allows it to see only the stack part of the variable, as shown in Figure 21-2.

How push_things sees an m_stack
Figure 21-2. How push_things sees an m_stack

Let’s improve the basic stack so that instead of always allocating a fixed-size stack, we allocate the stack dynamically. The new stack starts with:

class stack {
    private:
        int *data;    // Pointer to the data in the stack
    protected:
        int count;    // Current item on the stack
    public:
        stack(const unsigned int size) {
            data = new int[size];
            count = 0;
        };
        virtual ~stack(  ) {
            delete []data;
            data = NULL;
        }
// ...

(We discuss the keyword virtual later in this chapter.)

This stack is more flexible. To use the new stack, we must give it a size when we declare the stack variable. For example:

stack big_stack(1000);
stack small_stack(10);
stack bad_stack; // Illegal, size required

Back to the m_stack class: somehow we need to call the base class constructor (stack) with a parameter.

The way we do this is to put the base-constructor initialization just after the declaration of the constructor for the derived class.

But this flexibility creates some problems for the m_stack: the constructor for stack contains a parameter. How is the m_stack to initialize the simple stack?

The solution is to use a syntax similar to initializing a constant data member:

class m_stack: public stack {
    private:
        const unsigned int stack_size;   // Size of the simple stack
    public:
        m_stack(const unsigned int size) : stack(size),
                                           stack_size(size) {
        }

So expression stack(size) calls the constructor for stack while stack_size(size) initializes the constant data member stack_size. (Or if you’ve got a warped mind, you can think of stack_size(size) as calling the constructor for the integer constant stack_size.)

Note

Because the new version of stack uses dynamic memory (new and delete), it is vital that we define the “big four” member functions: the constructor, destructor, copy constructor, and assignment operator (=). When we use simple member variables to store our data, the default destructor would automatically reclaim all the memory we used. But now that we are using the heap, we must use delete to free the memory and that needs to be done in the destructor.

Virtual Functions

Today there are many different ways of sending a letter. We can mail it by the United States Postal Service, send it via Federal Express, or even fax it. All these methods get the letter to the person to whom you’re sending it (most of the time), but they differ in cost and speed.

Let’s define a class called mail to handle the sending of a letter. We start by defining an address class and then use this class to define addresses for the sender and the receiver. (The definition of the address class is “just a simple matter of programming” and is left to the reader.)

Our mail class looks like this:

class mail {
    public:
        address sender;   // Who's sending the mail (return address)?
        address receiver; // Who's getting the mail?

        // Send the letter
        void send_it(  ) {
        // ... Some magic happens here
        };
};

There is, however, one little problem with this class: we’re depending on “magic” to get our letters sent. The process for sending a letter is different depending on which service we are using. One way to handle this is to have send_it call the appropriate routine depending on what service we are using:

void mail::send_it(  ) {
    switch (service) {
        case POST_OFFICE:
            put_in_local_mailbox(  );
            break;
        case FEDERAL_EXPRESS:
            fill_out_waybill(  );
            call_federal_for_pickup(  );
            break;
        case UPS:
            put_out_ups_yes_sign(  );
            give_package_to_driver(  );
            break;
        //... and so on for every service in the universe

This solution is a bit clunky. Our mail class must know about all the mailing services in the world. Also consider what happens when we add another function to the class:

class mail {
    public:
        // Returns the cost of mailing in cents
        int cost(  ) {
        // ... more magic
        }

Do we create another big switch statement? If we do, we’ll have two of them to worry about. What’s worse, the sending instructions and cost for each service are now spread out over two functions. It would be nice if we could group all the functions for the Postal Service in one class, all of Federal Express in another class, and so on.

For example, a class for the Postal Service might be:

class post_office: public mail{
    public:
        // Send the letter
        void send_it(  ) {
            put_in_local_mailbox(  );
        };
        // Cost returns cost of sending a letter in cents
        int cost(  ) {
              // Costs 37 cents to mail a letter
             return (37);   // WARNING: This can easily become dated
        }
};

Now we have the information for each single service in a single class. The information is stored in a format that is easy to understand. The problem is that it is not easy to use. For example, let’s write a routine to send a letter:

void get_address_and_send(mail& letter)
{
    letter.from = my_address;
    letter.to = get_to_address(  );
    letter.send_it(  );
}
//...
    class post_office simple_letter;
    get_address_and_send(simple_letter);

The trouble is that letter is a mail class, so when we call letter.send_it( ), we call the send_it of the base class mail. What we need is a way of telling C++, “Please call the send function of the derived class instead of the base class.”

The virtual keyword identifies a member function that can be overridden by a member function in the derived class. If we are using a derived class, C++ will look for members in the derived class and then in the base class, in that order. If we are using a base class variable (even if the actual instance is a derived class), C++ will search only the base class for the member function. The exception is when the base class defines a virtual function. In this case, the derived class is searched and then the base class.

Table 21-1 illustrates the various search algorithms.

Table 21-1. Member function search order

Class type

Member function type

Search order

Derived

Normal

Derived, then base

Base

Normal

Base

Base

Virtual

Derived, then base

Example 21-2 illustrates the use of virtual functions.

Example 21-2. virt/virt.cpp
// Illustrates the use of virtual functions
#include <iostream>

class base {
    public:
        void a(  ) { std::cout << "base::a called
"; }
        virtual void b(  ) { std::cout << "base::b called
"; }
        virtual void c(  ) { std::cout << "base::c called
"; }
};

class derived: public base {
    public:
        void a(  ) { std::cout << "derived::a called
"; }
        void b(  ) { std::cout << "derived::b called
"; }
};

void do_base(base& a_base)
{
    std::cout << "Call functions in the base class
";

    a_base.a(  );
    a_base.b(  );
    a_base.c(  );
}

int main(  )
{
    derived a_derived;

    std::cout << "Calling functions in the derived class
";

    a_derived.a(  );
    a_derived.b(  );
    a_derived.c(  );

    do_base(a_derived);
    return (0);
}

The derived class contains three member functions. Two are self-defined: a and b. The third, c, is inherited from the base class. When we call a, C++ looks at the derived class to see whether that class defines the function. In this case it does, so the line:

a_derived.a(  );

outputs:

derived::a called

When b is called the same thing happens, and we get:

derived::b called

It doesn’t matter whether the base class defines a and b or not. C++ calls the derived class and goes no further.

However, the derived class doesn’t contain a member function named c. So when we reach the line:

a_derived.c(  );

C++ tries to find c in the derived class and fails. Then it tries to find the member function in the base class. In this case it succeeds and we get:

base::c called

Now let’s move on to the function do_base. Because it takes a base class as its argument, C++ restricts its search for member functions to the base class. So the line:

a_base.a(  );

outputs:

base::a called

But what happens when the member function b is called? This is a virtual function. That tells C++ that the search rules are changed. C++ first checks whether there is a b member function in the derived class; then C++ checks the base class. In the case of b, there is a b in the derived class, so the line:

a_base.b(  );

outputs:

derived::b called

The member function c is also a virtual function. Therefore, C++ starts by looking for the function in the derived class. In this case, the function is not defined there, so C++ then looks in the base class. The function is defined there, so we get:

base::c called

Now getting back to our mail. We need a simple base class that describes the basic mailing functions for each different type of service:

class mail {
    public:
        address sender; // Who is sending the mail (return address)?
        address receiver; // Who is getting the mail?

        // Send the letter
        virtual void send_it(  ) {
            std::cout <<
                  "Error: send_it not defined in derived class.
"
            exit (8);
        };
        // Cost of sending a letter in pennies
        virtual int cost(  ) {
            std::cout << "Error: cost not defined in derived class.
"
            exit (8);
        };
};

We can define a derived class for each different type of service. For example:

class post_office: public mail {
    public:
        void send_it(  ) {
            put_letter_in_box(  );
        }
        int cost(  ) {
            return (29);
        }
};

Now we can write a routine to send a letter and not have to worry about the details. All we have to do is call send_it and let the virtual function do the work.

The mail class is an abstraction that describes a generalized mailer. To associate a real mailing service, we need to use it as the base for a derived class. But what happens if the programmer forgets to put the right member functions in the derived class? For example:

class federal_express: public mail {
    public:
        void send_it(  ) {
            put_letter_in_box(  );
        }
        // Something is missing
};

When we try to find the cost of sending a letter via Federal Express, C++ will notice that there’s no cost function in federal_express and call the one in mail. The cost function in mail knows that it should never be called, so it spits out an error message and aborts the program. Getting an error message is nice, but getting it at compilation rather than during the run would be better.

C++ allows you to specify virtual functions that must be overridden in a derived class. For this example, the new, improved, abstract mailer is:

class mail {
    public:
        address sender;    // Who is sending the mail (return address)?
        address receiver;  // Who is getting the mail?

        // Send the letter
        virtual void send_it(  ) = 0;
        // Cost of sending a letter in pennies
        virtual int cost(  ) = 0;
};

The = 0 tells C++ that these member functions are pure virtual functions. That is, they can never be called directly. Any class containing one or more pure virtual functions is called an abstract class. If you tried to use an abstract class as an ordinary type, such as:

void send_package(  ) {
    mail a_mailer;   // Attempt to use an abstract class

you would get a compile-time error.

Virtual Classes

Let’s take a look at an auto repair shop. Our shop consists of two rooms. The first is the garage where they repair the cars, and the second is the office where all the paper work is done. Let’s design C++ classes for this situation.

Both the garage and the office are rooms, so we have a base class of room and derived classes of garage and office. In C++ our class definitions would be as follows:

class room {
    // .....
};

class garage : public room {
    // ....
};

class office : public room {
    // ....
};

The two classes garage and office when combined make up the business. The C++ class for this is:

class repair_shop: public garage, office {
   // ...
}

Figure 21-3 illustrates this class structure.

Two-room repair shop
Figure 21-3. Two-room repair shop

This works well for most repair shops. But what about the small guy that doesn’t have enough space for two rooms and must put a desk in the garage and use it as part of his office? In his case, he has one room which is part garage and part office.

Ideally our class diagram should look like Figure 21-4. But we need some way of telling C++, “Don’t generate two rooms because we have one room with two uses.” This is done by declaring the base class virtual. This keyword tells C++ that the class has multiple uses depending on which part we are taking about.

One-room repair shop
Figure 21-4. One-room repair shop
class room {
    // .... 
};

class garage_part : virtual public room {
    // ....
};

class office_part : virtual public room {
    // ....
};

public small_repair_shop: public office_part, garage_part {
   // ...
};

It should be noted that the classes for the garage section of the business and the office section have to know if they are occupying a room or part of a room.

The class room is used as the base for two derived classes; derived classes cause their base class’s constructor to be called to initialize the class. Does this mean that the constructor for room will be called twice? The answer is no. C++ is smart enough to know that room is used twice and to ignore the second initialization.

Function Hiding in Derived Classes

Example 21-3 defines a base class with the overloaded function do_it, which comes in both integer and floating-point versions. The program also defines a derived class that contains the single integer function do_it.

Example 21-3. doit/doit.cpp
class simple {
    public:
        int do_it(int i, int j) { return (i*j); }
        float do_it(float f) { return (f*2);}
};
class derived: public simple {
    public:
        int do_it(int i, int j) { return (i+j); }
};

Clearly, when we are using the derived class and we call the integer version of do_it, we are calling the one in the derived class. But what happens if we call the floating-point version? The derived class has no floating-point do_it. Normally, if we don’t have a member function in the derived class, C++ will look to the base class.

However, since a version of do_it is defined in the derived class, C++ will look to the derived class for all flavors of do_it. In other words, if one form of do_it is defined in the derived class, that locks out all forms of the function:

int main(  ) {
    derived test;       // Define a class for our testing
    int i;              // Test variable
    float f;            // Test variable

    i = test.do_it(1, 3);   // Legal; returns 4 (1 + 3)
    f = test.do_it(4.0);    // Illegal; "do_it(float)" not defined in 
                            // the class "derived"

Constructors and Destructors in Derived Classes

Constructors and destructors behave differently from normal member functions, especially when used with derived classes. When a derived-class variable is created, the constructor for the base class is called first, followed by the constructor for the derived class.

Example 21-4 defines a simple base class and uses it to create a derived class.

Example 21-4. cons/class.cpp
#include <iostream>

class base_class {
    public:
        base_class(  ) {
            std::cout << "base_class constructor called
";
        }
        ~base_class(  ) {
            std::cout << "base_class destructor called
";
        }
};
class derived_class:public base_class {
    public:
        derived_class(  ) {
            std::cout << "derived_class constructor called
";
        }
        ~derived_class(  ) {
            std::cout << "derived_class destructor called
";
        }
};

Now when we execute the code:

derived_class *sample_ptr = new derived_class;

the program prints:

base_class constructor called
derived_class constructor called

After the variable is destroyed, the destructors are called. The destructor for the derived class is called first, followed by the destructor for the base class. So when we destroy the variable with the statement:

delete sample_ptr;
sample_ptr = NULL;

we get:

derived_class destructor called
base_class destructor called

But C++ has a surprise lurking for us. Remember that derived-class objects can operate as base-class objects. For example:

base_class *base_ptr = new derived_class;

is perfectly legal. However, there is a problem when the variable is deleted:

delete base_ptr;
base_ptr = NULL;

You see, base_ptr is a pointer to a base class. At this point, all the code can see is the base class. There is no way for C++ to know that there is a derived class out there. So when the variable is deleted, C++ fails to call the derived class destructor.

The output of the delete statement is:

base_class destructor called

We have just tricked C++ into deleting a class without calling the proper destructor.

We need some way to tell C++, “Hey, there is a derived class out there and you might want to call its destructor.” The way we do this is to make the destructor for the base class a virtual function:

class base_class {
    public:
        base_class(  ) {
           std::cout << "base_class constructor called
";
        }
        virtual ~base_class(  ) {
           std::cout << "base_class destructor called
";
        }
};

The keyword virtual normally means, “Call the function in the derived class instead of the one in the base class.” For the destructor, it has a slightly different meaning. When C++ sees a virtual destructor, it will call the destructor of the derived class and then call the destructor of the base class.

So with the virtual destructor in place, we can safely delete the base_class variable, and the program will output the proper information:

derived_class destructor called
base_class destructor called

Question 21-1: Why does Example 21-1 fail when we delete the variable list_ptr? The program seems to get upset when it tries to callclear at line 21.

Example 21-5. blow/blow.cpp
#include <iostream>
#include <cstdlib>

class list {
    private:
        int item;       // Current item number

    public:
        virtual void clear(  ) = 0;

        void next_item(  ) {
            ++item;
        }

        list(  ) { 
            item = 0;
        }

        virtual ~list(  ) {
           clear(  );
        }
};

class list_of_integers : public list {
    public:
        int array[100];   // Place to store the items

        void clear(  ) {
            int i;      // Array index

            for (i = 0; i < 100; ++i) 
                array[i] = 0;
        }
};

int main(  )
{
    list_of_integers *list_ptr = new list_of_integers;

    // Cause problems
    delete list_ptr;
    list_ptr = NULL;
    return (0);
}

The dynamic_cast Operator

The dynamic_cast operator can be used to change a pointer or reference to a base class to that of the derived class and vice versa. This conversion is done in a safe manner. If you attempt to do an incorrect conversion, an exception is thrown. By default this aborts the program. (See Chapter 22 for information on exceptions.)

For example:

class room { ... };
class office: public class room { ... }
class garage: public class room { ... } 

void funct(room *ptr)
{
    // Correct and safe conversion -- no problem
    office *office_ptr = dynamic_cast<office *>(ptr);

    // Incorrect conversion -- throws an exception
    garage *other_ptr = dynamic_cast<office *>(ptr);
...
}

int main(  )
{
    office the_office;

    funct(&the_office);

The call to funct changes a pointer to a derived class (office) into a pointer to the base class (room). This is done automatically by C++.

The first dynamic_cast converts the pointer back to a pointer to the correct derrived class. The second tries to convert the object into a garage. Since the actual object is an office, the conversion fails and an exception is thrown.

Summary

Since programming began, programmers have been trying to find ways of building reusable code. C++, through the use of derived classes, allows you to build classes on top of existing code. This provides a great deal of flexibility and makes the code easier to organize and maintain.

Programming Exercises

Exercise 21-1: Combine the checkbook class of Exercise 13-2 with the queue class of Exercise 13-3 to implement a checkbook class that can print out the last ten entries of your checkbook.

Exercise 21-2: Define a “string-match” base class.

class string_matcher {
    public:
        // Returns true if string matches, false if not
        int match(const char *const match);
...

Define derived classes that match words, numbers, and blank strings.

Exercise 21-3: Define a base class shape that can describe any simple shape, such as a square, circle, or equilateral triangle. The size of all these shapes can be reduced to a single dimension.

Define derived classes for each of the three shapes.

Create a virtual function in the base class that returns the area of each shape.

Note that you will need to more precisely define what dimensions are stored in the base class. (Is the size in the base class for circle the radius of the circle or the diameter?)

Exercise 21-4: Write a base class called pet that describes any common household pet. Define two derived classes called fish and dog with items specific to that type of animal. Write pure virtual functions in the base class for operations that are common to both types of animals yet are handled in different ways by each of them.

Exercise 21-5: Write a base class number that holds a single integer value and contains one member function, print_it. Define three derived classes to print the value in hex, octal, and decimal.

Answers to Chapter Questions

Answer 21-1: Remember that destructors are called in the order derived class first, then base class. In this case, the destructor for the derived class, list_of_integers, is called to destroy the class. The class is gone.

Next, the destructor for the base class list is called. It calls the function clear. This is a pure virtual function, so C++ must call the clear function in the derived class. But the derived class isgone. There is no clear function. This makes C++ very upset and it aborts the program. (Actually, only good compilers will cause a program to abort. Others may do something really strange to your program.)

You should never call pure virtual functions from a destructor. Actually, be very careful when you call virtual functions of any type in a destructor.



[1] This example is a little artificial because we wanted to keep things simple. But the techniques presented here apply to more complex objects.

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

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