7-Classes

来源:互联网 发布:网络运营总监培训 编辑:程序博客网 时间:2024/06/03 21:24

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

  • The fundamental ideas behind classes are data abstraction and encapsulation.
    1. Data abstraction is a programming and design technique that relies on the separation of interface and implementation.
      -1- The interface of a class consists of the operations that users of the class can execute.
      -2- The implementation includes the class’ data members, the bodies of the functions that constitute the interface, and any functions needed to define the class that are not intended for general use.
    2. Encapsulation enforces the separation of a class’ interface and implementation. A class that is encapsulated hides its implementation: users of the class can use the interface but have no access to the implementation.
  • A class that uses data abstraction and encapsulation defines an abstract data type. In an abstract data type, the class designer cares how the class is implemented. Programmers who use the class need not know how the type works. They instead think abstractly about what the type does.

7.1. Defining Abstract Data Types

7.1.1. Designing the Sales_data Class

  • The interface to Sales_data consists of the following operations:
    • An isbn member function to return the object’s ISBN
    • A combine member function to add one Sales_data object into another
    • A function named add to add two Sales_data objects
    • A read function to read data from an istream into a Sales_data object
    • A print function to print the value of a Sales_data object on an ostream
  • A well-designed class has an interface that is intuitive and easy to use and has an implementation that is efficient enough for its intended use.

Exercises Section 7.1.1

Exercise 7.1

Write a version of the transaction-processing program from §1.6(p. 24) using the Sales_data class you defined for theExercises in §2.6.1(p. 72).

#include <iostream>#include <string>using std::cin;using std::cout;using std::endl;using std::string;struct Sales_data{    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};int main(){    Sales_data total;    if(cin >> total.bookNo >> total.units_sold >> total.revenue)    {        Sales_data trans;        while(cin >> trans.bookNo >> trans.units_sold >> trans.revenue)        {            if(total.bookNo == trans.bookNo)            {                total.units_sold += trans.units_sold;                total.revenue += trans.revenue;            }            else            {                cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;                total = trans;            }        }        cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl;    }    else    {        std::cerr << "No data?!" << std::endl;        return -1;    }    return 0;}

7.1.2. Defining the Revised Sales_data Class

  • Our class’s data members:
    • bookNo, a string representing the ISBN;
    • units_sold, an unsigned that says how many copies of the book were sold;
    • revenue, a double representing the total revenue for those sales.
  • Our class has 3 member functions: combine, isbn and avg_price(return the average price at which the books were sold). avg_price isn’t intended for general use, so it is part of the implementation, not part of the interface.
  • Member functions must be declared inside the class and they may be defined inside the class itself or outside the class body. Functions defined in the class are implicitly inline(§6.5.2).
    Nonmember functions that are part of the interface are declared and defined outside the class.
struct Sales_data{    string isbn() const    {        return bookNo;    }    Sales_data& combine(const Sales_data&);    double avg_price() const;    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};Sales_data add(const Sales_Data&, const Sales_data&);ostream& print(ostream&, const Sales_data&);istream& read(istream&, Sales_data&);

Introducing this

  • With one exception in §7.6, 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 to the members of the object on which the function was called. So, when isbn returns bookNo, it is returning total.bookNo.
  • Member functions access the object on which they were called through this. When we call a member function, this is initialized with the address of the object on which the function was invoked. 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
    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. Any direct use of a member of the class is assumed to be an implicit reference through this. When isbn uses bookNo, it is as if written
    this->bookNo.
  • Because this is intended to 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

  • By default, this is a const pointer to the nonconst version of the class type and we cannot bind this to a const object. This fact means we cannot call an ordinary member function on a const object. E.g., the type of this in a Sales_data member function is Sales_data *const.
  • Since isbn doesn’t change the object to which this points, so it would be more flexible if this were a pointer to const(§6.2.3).
  • 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 that cannot change the object on which they are called.
    Objects that are const, and references or pointers to const objects, may call only const member functions.
  • We can think of the body of isbn as if it were written as
// This code is illegal: we may not explicitly define the this pointer ourselvesstring Sales_data::isbn(const Sales_data *const this){    return this->isbn;}
  • The compiler processes classes in two steps: the member declarations are compiled first, after which the member function bodies, if any, are processed. 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

  • When we define a member function outside the class body, the return type, parameter list, and name must match the declaration in the class body. For const member function, 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{    return units_sold == 0 ? 0 : revenue / units_sold;}
  • The function name Sales_data::avg_price uses the scope operator(§1.2) to say that we define the avg_price function that is declared in 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.

Defining a Function to Return “This” Object

  • The combine function acts like the compound assignment operator(+=). The object that calls this function is the left-hand operand of the assignment. The right-hand operand is passed as an argument.
Sales_data& Sales_data::combine(const Sales_data &rhs){    units_sold += rhs.units_sold;    revenue += rhs.revenue;    return *this;}
  • When program calls
    total.combine(trans);
    the address of total is bound to the implicit this parameter and rhs is bound to trans. When combine executes
    units_sold += rhs.units_sold;
    the effect is to add total.units_sold and trans.units_sold, storing the result into total.units_sold.
  • The built-in assignment operators return their left-hand operand as an lvalue(§4.4). To return an lvalue, the combine function must return a reference(Sales_data&).
  • We need to use this to access the object as a whole:
    return *this;
    The return statement dereferences this to obtain the object on which the function is executing.

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).

struct Sales_data{    string isbn() const    {        return bookNo;    }    Sales_data& combine(const Sales_data &rhs);    double avg_price() const;    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};Sales_data& Sales_data::combine(const Sales_data &rhs){    units_sold += rhs.units_sold;    revenue += rhs.revenue;    return *this;}

Exercise 7.3

Revise your transaction-processing program from §7.1.1(p. 256) to use these members.

#include <iostream>#include <string>using std::cin;using std::cout;using std::string;struct Sales_data{    string isbn() const    {        return bookNo;    }    Sales_data& combine(const Sales_data &rhs);    double avg_price() const;    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};Sales_data& Sales_data::combine(const Sales_data &rhs){    units_sold += rhs.units_sold;    revenue += rhs.revenue;    return *this;}double Sales_data::avg_price() const{    return units_sold == 0 ? 0 : revenue / units_sold;}int main(){    Sales_data total;    if(cin >> total.bookNo >> total.units_sold >> total.revenue)    {        Sales_data trans;        while(cin >> trans.bookNo >> trans.units_sold >> trans.revenue)        {            if(total.bookNo == trans.bookNo)            {                total.combine(trans);            }            else            {                cout << total.bookNo << " " << total.units_sold << " " << total.revenue << '\n';                total = trans;            }        }        cout << total.bookNo << " " << total.units_sold << " " << total.revenue << '\n';    }    else    {        cout << "No data?!" << '\n';        return -1;    }    return 0;}

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. SubsequentExercises will incrementally add features to this class.

class Person{    string name;    string address;};

Exercise 7.5

Provide operations in your Person class to return the name and address. Should these functions be const? Explain your choice.

class Person{public:    // We should define get_xx functions as const because    // they don't modify the class members' value.    string get_name() const    {        return name_;    }    string get_address() const    {        return address_;    }private:    string name_;    string address_;};
  • Nonmember functions that are conceptually part of a class, but not defined inside the class, are typically declared(but not defined) in the same header as the class itself. So, users need to include only one file to use any part of the interface.

Defining the read and print Functions

istream& read(istream &is, Sales_data &item){    double price = 0;    is >> item.bookNo >> item.units_sold >> price;    item.revenue = price * item.units_sold;    return is;}ostream& print(ostream &os, const Sales_data &item){    os << item.isbn() << ' ' << item.units_sold << ' ' << item.revenue <<     ' ' << item.avg_price();    return os;}
  • Both read and write take a reference to their respective IO class types. The IO class types cannot be copied, so we may only pass them by reference. Because reading or writing to a stream changes that stream, both functions take ordinary references, not references to const.

Defining the add Function

Sales_data add(const Sales_data &lhs, const Sales_data &rhs){    Sales_data sum = lhs;    sum.combine(rhs);    return sum;}

Exercises Section 7.1.3

Exercise 7.6

Define your own versions of the add, read, and print functions.

istream& read(istream &is, Sales_data &item){    double price = 0;    is >> item.bookNo >> item.units_sold >> price;    item.revenue = price * item.units_sold;    return is;}ostream& print(ostream &os, const Sales_data &item){    os << item.isbn() << ' ' << item.units_sold << ' ' << item.revenue <<     ' ' << item.avg_price() << '\n';    return os;}Sales_data add(const Sales_data &lhs, const Sales_data &rhs){    Sales_data sum = lhs;    sum.combine(rhs);    return sum;}

Exercise 7.7

Rewrite the transaction-processing program you wrote for theExercises in §7.1.2(p. 260) to use these new functions.

#include <iostream>#include <string>using std::cin;using std::cout;using std::string;using std::istream;using std::ostream;struct Sales_data{    string isbn() const    {        return bookNo;    }    Sales_data& combine(const Sales_data &rhs);    double avg_price() const;    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};Sales_data& Sales_data::combine(const Sales_data &rhs){    units_sold += rhs.units_sold;    revenue += rhs.revenue;    return *this;}double Sales_data::avg_price() const{    return units_sold == 0 ? 0 : revenue / units_sold;}istream& read(istream &is, Sales_data &item){    double price = 0;    is >> item.bookNo >> item.units_sold >> price;    item.revenue = price * item.units_sold;    return is;}ostream& print(ostream &os, const Sales_data &item){    os << item.isbn() << ' ' << item.units_sold << ' ' << item.revenue <<     ' ' << item.avg_price() << '\n';    return os;}Sales_data add(const Sales_data &lhs, const Sales_data &rhs){    Sales_data sum = lhs;    sum.combine(rhs);    return sum;}int main(){    Sales_data total;    if(read(cin, total))    {        Sales_data trans;        while(read(cin, trans))        {            if(total.bookNo == trans.bookNo)            {                total.combine(trans);            }            else            {                print(cout, total);                total = trans;            }        }        print(cout, total);    }    else    {        cout << "No data?!" << '\n';        return -1;    }    return 0;}

Exercise 7.8

Why does read define its Sales_data parameter as a plain reference and print define its parameter as a reference to const?

  • Because read needs to modify the object, whereas print doesn’t modify the object.

Exercise 7.9

Add operations to read and print Person objects to the code you wrote for theExercises in §7.1.2(p. 260).

class Person{public:    string get_name() const    {        return name_;    }    string get_address() const    {        return address_;    }private:    string name_;    string address_;};istream& Read(istream &is, Person &obj){    is >> obj.name_ >> obj.address_;    return is;}ostream& Print(ostream &os, const Person &obj){    os << obj.name_ << ' ' << obj.address_ << '\n';    return os;}

Exercise 7.10

What does the condition in the following if statement do?
if(read(read(cin, data1), data2))

  • Read 2 objects one time.

7.1.4. Constructors

  • Classes control object initialization by defining one or more constructors whose job is to initialize the data members of a class object. A constructor is run whenever an object of a class type is created.
  • Constructors have the same name as the class and they have no return type. They have a(possibly empty) parameter list and a(possibly empty) function body. A class can have multiple constructors that must differ from each other in the number or types of their parameters.
  • Constructors may not be declared as const. When we create a const object of a class type, the object does not assume its constness until after the constructor completes the object’s initialization. So, constructors can write to const objects during their construction.

The Synthesized Default Constructor

  • Classes control default initialization by defining the default constructor that takes no arguments.
  • If class does not define any constructors, the compiler will implicitly define the default constructor for us. The compiler-generated constructor is known as the synthesized default constructor. For most classes, this synthesized constructor initializes each data member of the class as follows:
    1. If there is an in-class initializer(§2.6.1), use it to initialize the member.
    2. Otherwise, default-initialize(§2.2.1) the member.
  • Because Sales_data provides initializers for units_sold and revenue, the synthesized default constructor uses those values to initialize those members. It default initializes bookNo to the empty string.
  • A class should define its own default constructor:
    1. The compiler generates the default for us only if we don’t define any other constructors.
    2. For some classes, the synthesized default constructor does the wrong thing. Objects of built-in or compound type(array/pointer…) that are defined inside a block have undefined value when they are default initialized. The same rule applies to members of built-in type that are default initialized. So, classes that have members of built-in or compound type should either initialize those members inside the class or define their own version of the default constructor. Otherwise, users could create objects with members that have undefined value.(Classes that have members of built-in or compound type can rely on the synthesized default constructor only if all such members have in-class initializers.)
    3. Sometimes the compiler is unable to synthesize one. E.g., if a class has a member that has a class type, and that class doesn’t have a default constructor, then the compiler can’t initialize that member. For such classes, we must define our own version of the default constructor. Otherwise, the class will not have a usable default constructor. §13.1.6: additional circumstances that prevent the compiler from generating an appropriate default constructor.

Defining the Sales_data Constructors

  • For our Sales_data class we’ll define four constructors with the following parameters:
    • An istream& from which to read a transaction.
    • A const string& representing an ISBN, an unsigned representing the count of how many books were sold, and a double representing the price at which the books sold.
    • A const string& representing an ISBN. This constructor will use default values for the other members.
    • An empty parameter list(the default constructor) which we must define because we have defined other constructors.
struct Sales_data{    Sales_data() = default;    Sales_data(const string &str) : bookNo(str) {}    Sales_data(const string &str, unsigned n, double p) :        bookNo(str), units_sold(n), revenue(p * n) {}    Sales_data(istream &);    string isbn() const    {        return bookNo;    }    Sales_data& combine(const Sales_data &rhs);    double avg_price() const;    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};

What = default Means

  • Sales_data() = default;
    C++11: We can ask the compiler to generate the default constructor for us by writing = default after the parameter list.
  • = default can appear with the declaration inside the class body or on the definition outside the class body.
    1. If inside the class body: the default constructor will be inlined;
    2. If appears on the definition outside the class, the member will not be inlined by default.

Constructor Initializer List

  • Constructor initializer list specifies initial values for one or more data members of the object being created. The constructor initializer is a list of member names, each of which is followed by that member’s initial value in parentheses(or inside curly braces). Multiple member initializations are separated by commas.
  • When a member is omitted from the constructor initializer list, it is implicitly initialized using the same process as is used by the synthesized default constructor.
  • Constructors should not override in-class initializers except to use a different initial value.

Defining a Constructor outside the Class Body

Sales_data::Sales_data(istream &is){    read(is, *this);}
  • Though the constructor initializer list is empty, the members of this object are initialized before the constructor body is executed. Members that don’t appear in the constructor initializer list are initialized by the corresponding in-class initializer(if there is one) or are default initialized.

Exercises Section 7.1.4

Exercise 7.11

Add constructors to your Sales_data class and write a program to use each of the constructors.

#include <iostream>#include <string>using std::cin;using std::cout;using std::string;using std::istream;using std::ostream;struct Sales_data{    Sales_data() = default;    Sales_data(const string &str) : bookNo(str) {}    Sales_data(const string &str, unsigned n, double p) :        bookNo(str), units_sold(n), revenue(p * n) {}    Sales_data(istream &);    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};istream& read(istream &is, Sales_data &item){    double price = 0;    is >> item.bookNo >> item.units_sold >> price;    item.revenue = price * item.units_sold;    return is;}ostream& print(ostream &os, const Sales_data &item){    os << item.isbn() << ' ' << item.units_sold << ' ' << item.revenue <<     ' ' << item.avg_price() << '\n';    return os;}Sales_data::Sales_data(istream &is){    read(is, *this);}int main(){    Sales_data obj1;    Sales_data obj2("gaoxiang");    Sales_data obj3("gao", 71, 8.8);    Sales_data obj4(cin);    print(cout, obj1);    print(cout, obj2);    print(cout, obj3);    print(cout, obj4);    return 0;}

Exercise 7.12

Move the definition of the Sales_data constructor that takes an istream into the body of the Sales_data class.

#include <iostream>#include <string>using std::string;using std::istream;struct Sales_data;istream& read(istream &is, Sales_data &item);struct Sales_data{    Sales_data() = default;    Sales_data(const string &str) : bookNo(str) {}    Sales_data(const string &str, unsigned n, double p) :        bookNo(str), units_sold(n), revenue(p * n) {}    Sales_data(istream &is)    {        read(is, *this);    }    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};

Exercise 7.13

Rewrite the program from page 255 to use the istream constructor.

#include <iostream>#include <string>using std::cin;using std::cout;using std::string;using std::istream;using std::ostream;struct Sales_data;istream& read(istream &is, Sales_data &item);struct Sales_data{    Sales_data() = default;    Sales_data(const string &str) : bookNo(str) {}    Sales_data(const string &str, unsigned n, double p) :        bookNo(str), units_sold(n), revenue(p * n) {}    Sales_data(istream &is)    {        read(is, *this);    }    string isbn() const    {        return bookNo;    }    Sales_data& combine(const Sales_data &rhs);    double avg_price() const;    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};Sales_data& Sales_data::combine(const Sales_data &rhs){    units_sold += rhs.units_sold;    revenue += rhs.revenue;    return *this;}double Sales_data::avg_price() const{    return units_sold == 0 ? 0 : revenue / units_sold;}istream& read(istream &is, Sales_data &item){    double price = 0;    is >> item.bookNo >> item.units_sold >> price;    item.revenue = price * item.units_sold;    return is;}ostream& print(ostream &os, const Sales_data &item){    os << item.isbn() << ' ' << item.units_sold << ' ' << item.revenue <<     ' ' << item.avg_price() << '\n';    return os;}int main(){    Sales_data total(cin);    if(total.bookNo.empty() == false)    {        while(cin)        {            Sales_data trans(cin);            if(total.bookNo == trans.bookNo)            {                total.combine(trans);            }            else            {                print(cout, total);                total = trans;            }        }        print(cout, total);    }    else    {        cout << "No data?!" << '\n';        return -1;    }    return 0;}

Exercise 7.14

Write a version of the default constructor that explicitly initializes the members to the values we have provided as in-class initializers.

Sales_data() : units_sold(0) , revenue(0){}

Exercise 7.15

Add appropriate constructors to your Person class.

class Person{public:    Person() = default;    Person(const string name, const string address) :        name_(name), address_(address) { }    Person(istream &is)    {        read(is, *this);    }    string get_name() const    {        return name_;    }    string get_address() const    {        return address_;    }private:    string name_;    string address_;};istream& Read(istream &is, Person &obj){    is >> obj.name_ >> obj.address_;    return is;}ostream& Print(ostream &os, const Person &obj){    os << obj.name_ << ' ' << obj.address_ << '\n';    return os;}

7.1.5. Copy, Assignment, and Destruction

  • Objects are copied in several contexts: when we initialize a variable or when we pass or return an object by value.
    Objects are assigned when we use the assignment operator.
    Objects are destroyed when they cease to exist, such as when a local object is destroyed on exit from the block in which it was created. Objects stored in a vector/ array are destroyed when that vector/array is destroyed.
  • If we don’t define these operations, the compiler will synthesize them for us. Ordinarily, the versions that the compiler generates for us execute by copying, assigning, or destroying each member of the object.

Some Classes Cannot Rely on the Synthesized Versions

  • The synthesized copy/assignment/destruction versions can’t work correctly for classes that allocate resources that reside outside the class objects themselves.
  • Classes that need dynamic memory should use a vector or a string to manage the necessary storage because this can avoid the complexities involved in allocating and deallocating memory.
  • The synthesized versions for copy, assignment, and destruction work correctly for classes that have vector or string members. When we copy or assign an object that has a vector member, the vector class takes care of copying or assigning the elements in that member. When the object is destroyed, the vector member is destroyed, which in turn destroys the elements in the vector. Similarly for strings.

7.2. Access Control and Encapsulation

  • We use access specifiers to enforce encapsulation:
    • Members defined after public are accessible to all parts of the program. The public members define the interface to the class.
    • Members defined after private are accessible to the member functions of the class but are not accessible to code that uses the class. The private sections encapsulate the implementation.
class Sales_data{public:    Sales_data() = default;    Sales_data(const string &str) : bookNo(str) {}    Sales_data(const string &str, unsigned n, double p) :        bookNo(str), units_sold(n), revenue(p * n) {}    Sales_data(istream &is)    {        read(is, *this);    }    string isbn() const    {        return bookNo;    }    Sales_data& combine(const Sales_data &rhs);private:    double avg_price() const    {        return units_sold == 0 ? 0 : revenue / units_sold;    }    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};
  • A class may contain zero or more access specifiers, and there are no restrictions on how often an access specifier may appear. Each access specifier specifies the access level of the succeeding members. The specified access level remains in effect until the next access specifier or the end of the class body.

Using the class or struct Keyword

  • The only difference between struct and class is the default access level. struct: the members defined before the first access specifier are public; class: private.
  • When we define a class intending for all of its members to be public, we use struct. If we intend to have private members, then we use class.

Key Concept: Benefits of Encapsulation

  • Encapsulation advantages:
    1. User code cannot inadvertently corrupt the state of an encapsulated object.
    2. The implementation of an encapsulated class can change over time without requiring changes in user-level code.
  • Although user code need not change when a class definition changes, the source files that use a class must be recompiled any time the class changes.

Exercises Section 7.2

Exercise 7.16

What, if any, are the constraints on where and how often an access specifier may appear inside a class definition? What kinds of members should be defined after a public specifier? What kinds should be private?

  • No limits.
  • The public members define the interface to the class.
  • The private sections encapsulate the implementation.

Exercise 7.17

What, if any, are the differences between using class or struct?

  • The default access level. struct: the members defined before the first access specifier are public; class: private.

Exercise 7.18

What is encapsulation? Why is it useful?

  • Encapsulation enforces the separation of a class’ interface and implementation. A class that is encapsulated hides its implementation: users of the class can use the interface but have no access to the implementation.
  • Advantages:
    1. User code cannot inadvertently corrupt the state of an encapsulated object.
    2. The implementation of an encapsulated class can change over time without requiring changes in user-level code.

Exercise 7.19

Indicate which members of your Person class you would declare as public and which you would declare as private. Explain your choice.

  • public: constructors, get_name(), get_address().
    private: name_, address_.
  • The interface should be defined as public, the data shouldn’t expose to outside of the class.

7.2.1. Friends

  • Class can allow another class or function to access its nonpublic members by making that class or function a friend. A class makes a function its friend by including a declaration for that function preceded by the keyword friend:
class Sales_data{    friend istream& read(istream &, Sales_data &);    friend ostream& print(ostream &, const Sales_data &);    friend Sales_data add(const Sales_data &, const Sales_data &);public:    ...private:    ...};
  • Friend declarations can appear only inside a class definition; they may appear anywhere in the class. Friends are not members of the class and are not affected by the access control of the section in which they are declared. It is good to group friend declarations together at the beginning or end of the class definition.

Declarations for Friends

  • A friend declaration only specifies access. If we want users of the class to be able to call a friend function, we must also declare the function separately from the friend declaration. To make a friend visible to users of the class, we declare each friend outside the class in the same header as the class itself.
  • Some compilers allow calls to a friend function when there is no ordinary declaration for that function. It is a good idea to provide separate declarations for friends, so you won’t have to change your code if you use a compiler that enforces this rule.

Exercises Section 7.2.1

Exercise 7.20

When are friends useful? Discuss the pros and cons of using friends.

  • Class can allow another class or function to access its nonpublic members by making that class or function a friend.
  • Pros:
    1. The useful functions can refer to class members in the class scope without needing to explicitly prefix them with the class name.
    2. You can access all the nonpublic members conveniently.
  • Cons:
    Lessens encapsulation and therefore maintainability.
    Code verbosity, declarations inside the class, outside the class.

Exercise 7.21

Update your Sales_data class to hide its implementation. The programs you’ve written to use Sales_data operations should still continue to work. Recompile those programs with your new class definition to verify that they still work.

#include <iostream>#include <string>using std::cin;using std::cout;using std::string;using std::istream;using std::ostream;class Sales_data;istream& read(istream &, Sales_data &);ostream& print(ostream &, const Sales_data &);Sales_data add(const Sales_data &, const Sales_data &);class Sales_data{    friend istream& read(istream &, Sales_data &);    friend ostream& print(ostream &, const Sales_data &);    friend Sales_data add(const Sales_data &, const Sales_data &);public:    Sales_data() = default;    Sales_data(const string &str) : bookNo(str) {}    Sales_data(const string &str, unsigned n, double p) :        bookNo(str), units_sold(n), revenue(p * n) {}    Sales_data(istream &is)    {        read(is, *this);    }    string isbn() const    {        return bookNo;    }    Sales_data& combine(const Sales_data &rhs);private:    double avg_price() const    {        return units_sold == 0 ? 0 : revenue / units_sold;    }    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};Sales_data& Sales_data::combine(const Sales_data &rhs){    units_sold += rhs.units_sold;    revenue += rhs.revenue;    return *this;}istream& read(istream &is, Sales_data &item){    double price = 0;    is >> item.bookNo >> item.units_sold >> price;    item.revenue = price * item.units_sold;    return is;}ostream& print(ostream &os, const Sales_data &item){    os << item.isbn() << ' ' << item.units_sold << ' ' << item.revenue <<     ' ' << item.avg_price() << '\n';    return os;}Sales_data add(const Sales_data &lhs, const Sales_data &rhs){    Sales_data sum = lhs;    sum.combine(rhs);    return sum;}int main(){    Sales_data total(cin);    if(total.isbn().empty() == false)    {        while(cin)        {            Sales_data trans(cin);            if(total.isbn() == trans.isbn())            {                total.combine(trans);            }            else            {                print(cout, total);                total = trans;            }        }        print(cout, total);    }    else    {        cout << "No data?!" << '\n';        return -1;    }    return 0;}

Exercise 7.22

Update your Person class to hide its implementation.

class Person;istream& Read(istream &, Person &);ostream& Print(ostream &, const Person &);class Person{    friend istream& Read(istream &, Person &);    friend ostream& Print(ostream &, const Person &);public:    Person() = default;    Person(const string name, const string address) :        name_(name), address_(address) { }    Person(istream &is)    {        Read(is, *this);    }    string get_name() const    {        return name_;    }    string get_address() const    {        return address_;    }private:    string name_;    string address_;};istream& Read(istream &is, Person &obj){    is >> obj.name_ >> obj.address_;    return is;}ostream& Print(ostream &os, const Person &obj){    os << obj.name_ << ' ' << obj.address_ << '\n';    return os;}

7.3. Additional Class Features

7.3.1. Class Members Revisited

Defining a Type Member

  • A Screen represents a window on a display. Each Screen has a string member that holds the Screen’s contents, and three string::size_type members that represent the position of the cursor, and the height and width of the screen.
  • Type names defined by a class are subject to the same access controls as any other member and may be either public or private:
class Screen{public:    typedef string::size_type   pos; // Equivalent to: using pos = string::size_type;private:    pos cursor = 0;    pos height = 0, width = 0;    string contents;};
  • Unlike ordinary members, members that define types must appear before they are used(explain in §7.4.1).

Member Functions of class Screen

class Screen{public:    typedef string::size_type   pos;    Screen() = default;    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}    char get() const    {        return contents[cursor];    }    inline char get(pos ht, pos wd) const;    Screen &move(pos r, pos c);private:    pos cursor = 0;    pos height = 0, width = 0;    string contents;};

Making Members inline

  • Member functions defined inside the class are automatically inline(§6.5.2). We can explicitly declare a member function as inline as part of its declaration inside the class body. We can specify inline on the function definition that appears outside the class body.
inline Screen& Screen::move(pos r, pos c){    pos row = r * width;    cursor = row + c;    return *this;}char Screen::get(pos r, pos c) const{    pos row = r * width;    return contents[row + c];}

Overloading Member Functions

  • Member functions may be overloaded so long as the functions differ by the number and/or types of parameters. The same function-matching(§6.4) process is used for calls to member functions as for nonmember functions.

mutable Data Members

  • A mutable data member is never const, even when it is a member of a const object. A const member function can change a mutable member. We give Screen a mutable member named access_ctr, which use to track how often each Screen member function is called:
class Screen{public:    void some_member() const    {        ++access_ctr;    }private:    mutable size_t access_ctr;};

Initializers for Data Members of Class Type

  • We define a window manager class that represents a collection of Screens on a given display. This class has a vector of Screens in which each element represents a particular Screen. We’d like Window_mgr class to start up with a default-initialized Screen. Under C++11, we use an in-class initializer(§2.6.1) to specify this default value.
class Window_mgr{private:    vector<Screen> screens{Screen(24, 80, ' ')};};
  • When we initialize a member of class type, we are supplying arguments to a constructor of that member’s type. In this case, we list initialize vector member with a single element initializer which contains a Screen value that is passed to the vector constructor to create a one-element vector.
  • In-class initializers must use either the = form of initialization or the direct form of initialization using curly braces.

Exercises Section 7.3.1

Exercise 7.23

Write your own version of the Screen class.

#include <iostream>#include <string>#include <vector>using std::cin;using std::cout;using std::string;using std::vector;class Screen{public:    typedef string::size_type   pos;    Screen() = default;    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}    char get() const    {        return contents[cursor];    }    inline char get(pos ht, pos wd) const;    Screen &move(pos r, pos c);    void some_member() const    {        ++access_ctr;    }private:    pos cursor = 0;    pos height = 0, width = 0;    string contents;    mutable size_t access_ctr;};inline Screen& Screen::move(pos r, pos c){    pos row = r * width;    cursor = row + c;    return *this;}char Screen::get(pos r, pos c) const{    pos row = r * width;    return contents[row + c];}int main(){    Screen obj(10, 10, 'g');    return 0;}

Exercise 7.24

Give your Screen class three constructors: a default constructor; a constructor that takes values for height and width and initializes the contents to hold the given number of blanks; and a constructor that takes values for height, width, and a character to use as the contents of the screen.

Screen() = default;Screen(pos ht, pos wd) : height(ht), width(wd), contents(ht * wd, ' ') {}Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}

Exercise 7.25

Can Screen safely rely on the default versions of copy and assignment? If so, why? If not, why not?

  • Yes. The synthesized versions for copy, assignment, and destruction work correctly for classes that have vector or string members.

Exercise 7.26

Define Sales_data::avg_price as an inline function.

inline double avg_price() const{    return units_sold == 0 ? 0 : revenue / units_sold;}

7.3.2. Functions That Return *this

  • Next we’ll add functions to set the character at the cursor or at a given location:
Screen &set(char c){    contents[cursor] = c;    return *this;}Screen &set(pos r, pos col, char ch){    contents[r * width + col] = ch;    return *this;}
  • Functions that return a reference are lvalues(§6.3.2).

Returning *this from a const Member Function

  • We add display to print the contents of the Screen. Displaying a screen doesn’t change the object, so we should make display a const member and this in display is a pointer to const and *this is a const object.

Overloading Based on const

  • We can overload a member function based on whether it is const. We can call only const member functions on a const object, call either version on a nonconst object(the nonconst version is a better match).
class Screen{public:    Screen &display(ostream &os)    {        do_display(os);        return *this;    }    const Screen &display(ostream &os) const    {        do_display(os);        return *this;    }private:    void do_display(ostream &os) const    {        os << contents << '\n';    }};
  • The nonconst version of display returns an nonconst reference; the const member returns a reference to const.
  • We define a separate do_display operation for following reasons:
    1. A general desire to avoid writing the same code in more than one place.
    2. As the display action become more complicated, it makes sense to write those actions in one place, not two.
    3. There needn’t be any overhead involved in this extra function call. We defined do_display inside the class body, so it is implicitly inline. So, there is no run-time overhead associating with calling do_display.

Exercises Section 7.3.2

Exercise 7.27

Add the move, set, and display operations to your version of Screen. Test your class by executing the following code:

Screen myScreen(5, 5, 'X');myScreen.move(4,0).set('#').display(cout);cout << "\n";myScreen.display(cout);cout << "\n";
#include <iostream>#include <string>#include <vector>using std::cin;using std::cout;using std::string;using std::vector;using std::ostream;class Screen{public:    typedef string::size_type   pos;    Screen() = default;    Screen(pos ht, pos wd) : height(ht), width(wd), contents(ht * wd, ' ') {}    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}    char get() const    {        return contents[cursor];    }    inline char get(pos ht, pos wd) const;    Screen &set(char c)    {        contents[cursor] = c;        return *this;    }    Screen &set(pos r, pos col, char ch)    {        contents[r * width + col] = ch;        return *this;    }    Screen &move(pos r, pos c);    void some_member() const    {        ++access_ctr;    }    Screen &display(ostream &os)    {        do_display(os);        return *this;    }    const Screen &display(ostream &os) const    {        do_display(os);        return *this;    }private:    void do_display(ostream &os) const    {        os << contents << '\n';    }    pos cursor = 0;    pos height = 0, width = 0;    string contents;    mutable size_t access_ctr;};inline Screen& Screen::move(pos r, pos c){    pos row = r * width;    cursor = row + c;    return *this;}char Screen::get(pos r, pos c) const{    pos row = r * width;    return contents[row + c];}int main(){    Screen obj(5, 5, 'X');    obj.move(4, 0).set('#').display(cout);    obj.display(cout);    return 0;}

Exercise 7.28

What would happen in the previous###Exercise if the return type of move, set, and display was Screen rather than Screen&?

  • Our object doesn’t change at all.

Exercise 7.29

Revise your Screen class so that move, set, and display functions return Screen and check your prediction from the previousExercise.

  • Yes.
    XXXXXXXXXXXXXXXXXXXX#XXXX
    XXXXXXXXXXXXXXXXXXXXXXXXX

Exercise 7.30

It is legal but redundant to refer to members through the this pointer. Discuss the pros and cons of explicitly using the this pointer to access members.

  • Pros
    1. More explicit.
    2. Can use the member function parameter which name is same as the member name.
void set(const std::string &addr){    this->addr = addr;}
  • Cons: More to read, redundant

7.3.3. Class Types

  • Every class defines a unique type. Two different classes define two different types even if they define the same members. We can refer to a class type directly by using the class name as a type name or we can use the class name following the keyword class or struct.
Sales_data item1;       // default-initialized object of type Sales_dataclass Sales_data item1; // equivalent declaration

Class Declarations

class Screen; // declaration of the Screen class
  • Class declaration(known as forward declaration) introduces the name Screen into the program and indicates that Screen refers to a class type. After a declaration and before a definition is seen, we know that Screen is a class type but not known what members that type contains.
  • We can use an incomplete type in limited ways: We can define pointers or references to such types, and we can declare(but not define) functions that use an incomplete type as a parameter or return type.
  • A class must be defined before we can create objects of that type because the compiler needs know how much storage such objects need.
    The class must be defined before a reference or pointer is used to access a member of the type because the compiler can’t know what members the class has until it is defined.
    Data members can be specified to be of a class type only if the class has been defined because the compiler needs to know how much storage the data member requires(one exception in §7.6).
  • Because a class is not defined until its class body is complete, a class cannot have data members of its own type. Since a class is considered declared(but not yet defined) as soon as its class name has been seen, a class can have data members that are pointers or references to its own type.
class Link_screen{    Screen window;    Link_screen *next;    Link_screen *prev;};

Exercises Section 7.3.3

Exercise 7.31

Define a pair of classes X and Y, in which X has a pointer to Y, and Y has an object of type X.

#include <iostream>class Y;class X{    Y *ptr;};class Y{    X obj;};int main(){X obj_x;Y obj_y;return 0;}

7.3.4. Friendship Revisited

  • A class can make another class its friend or it can declare specific member functions of another previously defined class as friends. A friend function can be defined inside the class body and they are implicitly inline.

Friendship between Classes

  • The member functions of a friend class can access all the members(including the nonpublic members) of the class granting friendship.
class Screen{    friend class Window_mgr;    ...}class Window_mgr{public:    using ScreenIndex = vector<Screen>::size_type;    void clear(ScreenIndex i)    {        Screen &s = screens[i];        s.contents = string(s.height * s.width, ' ');    }private:    vector<Screen> screens {Screen(24, 80, ' ')};};
  • Friendship is not transitive: if Window_mgr has its own friends, those friends have no special access to Screen.

Making A Member Function a Friend

  • Screen can specify that only the clear member is allowed access. When we declare a member function to be a friend, we must specify the class of which that function is a member.
class Screen{    // Window_mgr::clear must have been declared before class Screen    friend void Window_mgr::clear(ScreenIndex);    // ... rest of the Screen class};
  • Making a member function a friend requires structuring of programs to accommodate interdependencies among the declarations and definitions. We must order our program as follows:
    1. Define Window_mgr class, which declares but not define clear(). Screen must be declared before clear can use the members of Screen.
    2. Define class Screen, including a friend declaration for clear.
    3. Define clear, which can now refer to the members in Screen.

Overloaded Functions and Friendship

  • A class must declare as a friend each function in a set of overloaded functions that it wishes to make a friend.
// overloaded Fun functionsextern std::ostream& Fun(std::ostream &, Screen &);extern bitmap& Fun(bitmap &, Screen &);class Screen{    // ostream version of Fun may access the private parts of    Screen objects    friend std::ostream& Fun(std::ostream &, Screen &);    // . . .};
  • Class Screen makes the version of Fun that takes an ostream& its friend, the version that takes a bitmap& has no special access to Screen.

Friend Declarations and Scope

  • Classes and nonmember functions need not have been declared before they are used in a friend declaration.
  • Even if we define the function inside the class, we must still provide a declaration outside of the class itself to make that function visible. A declaration must exist even if we only call the friend from members of the friendship granting class.
struct X{    friend void f() {}    X()    {        f();//error: ‘f’ was not declared in this scope    }    void g();    void h();};void X::g(){    return f();//error: ‘f’ was not declared in this scope}void f();// declares the function defined inside Xvoid X::h(){    return f();// ok: declaration for f is now in scope}

Exercises Section 7.3.4

Exercise 7.32

Define your own versions of Screen and Window_mgr in which clear is a member of Window_mgr and a friend of Screen.

#include <iostream>#include <string>#include <vector>using std::cin;using std::cout;using std::string;using std::vector;using std::ostream;class Screen;class Window_mgr{public:    using ScreenIndex = vector<Screen>::size_type;    void clear(ScreenIndex i);private:    vector<Screen> screens;};class Screen{    friend void Window_mgr::clear(ScreenIndex);public:    typedef string::size_type   pos;    Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}private:    pos cursor = 0;    pos height = 0, width = 0;    string contents;};void Window_mgr::clear(ScreenIndex i){    Screen &s = screens[i];    s.contents = string(s.height * s.width, ' ');}int main(){    return 0;}

7.4. Class Scope

  • Every class defines its own new scope. Outside the class scope, ordinary data and function members can be accessed only through an object, a reference, or a pointer using a member access operator. We access type members from the class using the scope operator.
Screen::pos ht = 24, wd = 80;   // use the pos type defined by ScreenScreen screen(ht, wd, ' ');Screen *p = &screen;char c = screen.get();  // fetches the get member from the object scrc = p->get();           // fetches the get member from the object to which p points
  • The fact that a class is a scope explains why we must provide the class name as well as the function name when we define a member function outside its class. Outside of the class, the names of the members are hidden. Once the class name is seen, the remainder of the definition(including the parameter list and the function body) is in the scope of the class and we can refer to other class members without qualification.
  • Since the return type of a function appears before the function’s name, when a member function is defined outside the class body, any name used in the return type is outside the class scope. So, the return type must specify the class of which it is a member.
class Window_mgr{public:    ScreenIndex addScreen(const Screen&);private:    vector<Screen> screens {Screen(24, 80, ' ')};};Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s){    screens.push_back(s);    return screens.size() - 1;}

Exercises Section 7.4

Exercise 7.33

What would happen if we gave Screen a size member defined as follows? Fix any problems you identify.

pos Screen::size() const{    return height * width;}
  • Screen::pos

7.4.1. Name Lookup and Class Scope

  • Name lookup(the process of finding which declarations match the use of a name):
    1. Look for a declaration of the name in the block in which the name was used. Only names declared before the use are considered.
    2. If the name isn’t found, look in the enclosing scope(s).
    3. If no declaration is found, then the program is in error.
  • The way names are resolved inside member functions defined inside the class: Class definitions are processed in two phases:
    1. The member declarations are compiled.
    2. Function bodies are compiled only after the entire class has been seen.

Name Lookup for Class Member Declarations

  • This two-step process applies only to names used in the body of a member function. Names used in declarations(including names used for the return type and types in the parameter list) must be seen before they are used. If a member declaration uses a name that has not yet been seen inside the class, the compiler will look for that name in the scope(s) in which the class is defined.
typedef double Money;string bal;class Account{public:    Money balance()    {        return bal;    }private:    Money bal = 71.88;};int main(){    Account obj;    cout << obj.balance();// 71.88    return 0;}
  • When the compiler sees the declaration of the balance function, it will look for a declaration of Money in the Account class. The compiler considers only declarations inside Account that appear before the use of Money. Because no matching member is found, the compiler then looks for a declaration in the enclosing scope(s). The compiler will find the typedef of Money and that type will be used for the return type of the function balance and as the type for the data member bal.
  • The function body of balance is processed only after the entire class is seen. So, the return inside that function returns the member named bal, not the string from the outer scope.

Type Names Are Special

  • Ordinarily, an inner scope can redefine a name from an outer scope even if that name has already been used in the inner scope. But in class, if a member uses a name from an outer scope and that name is a type, then the class can’t subsequently redefine that name.
typedef double Money;string bal;class Account{public:    //typedef double Money;    Money balance()    {        return bal;    }private:    typedef double Money; //error: declaration of ‘typedef double Account::Money’    Money bal = 71.88;};
  • Though it is an error to redefine a type name, some compilers will accept such code, even though the program is in error.
  • Definitions of type names should appear at the beginning of a class. So, any member that uses that type will be seen after the type name has already been defined.

Normal Block-Scope Name Lookup inside Member Definitions

  • A name used in the body of a member function is resolved as follows:
    1. Look for a declaration of the name inside the member function: only declarations in the function body that precede the use of the name are considered(including parameters).
    2. If not found inside the member function, look for a declaration inside the class. All the members of the class are considered.
    3. If not found in the class, look for a declaration that is in scope before the member function definition.
int height;class Screen{public:    typedef std::string::size_type pos;    void dummy_fcn(pos height)    {        cursor = width * height; // The parameter “height”    }private:    pos height = 0, width = 0;};
  • The height parameter hides the member named height. If we wanted to override the normal lookup rules, we can do so:
void Screen::dummy_fcn(pos height){    cursor = width * this->height;      // member height    // alternative way to indicate the member    cursor = width * Screen::height;    // member height}
  • If we want the name from the outer scope, we can ask for it explicitly using the scope operator:
void Screen::dummy_fcn(pos height){    cursor = width * ::height;  // The global "height"}

Exercises Section 7.4.1

Exercise 7.34

What would happen if we put the typedef of pos in the Screen class on page 285 as the last line in the class?

  • There is an error in dummy_fcn(pos height); // unknown type name 'pos'

Exercise 7.35

Explain the following code, indicating which definition of Type or Init_Val is used for each use of those names. Say how you would fix any errors.

typedef string Type;Type Init_Val();class Exercise{public:    typedef double Type;// error    Type Set_Val(Type);    Type Init_Val();private:    int val;};Type Exercise::Set_Val(Type parameter)// Type is string,{    val = parameter + Init_Val();// val is: int; Init_Val is the class member    return val;}
#include <iostream>#include <string>using std::cout;using std::string;typedef string Type;Type Init_Val();class Exercise{public:    Type Set_Val(Type);    Type Init_Val()    {        return "gaoxiangnumber1";    }private:    int val;};Type Exercise::Set_Val(Type parm){    Type val = parm + Init_Val();    return val;}int main(){    ###Exercise obj;    cout << obj.Set_Val("hello ");    return 0;}

7.5. Constructors Revisited

7.5.1. Constructor Initializer List

  • By the time the body of the constructor begins executing, initialization is complete. If we don’t initialize a member in the constructor initializer list, that member is default initialized before the constructor body starts executing.
  • We must use the constructor initializer list to provide values for members that are const, reference, or of a class type that does not have a default constructor.
class ConstRef{public:    // error: ci and ri must be initialized    ConstRef(int ii)    {        // assignments:        i = ii; // ok        ci = ii;    // error: cannot assign to a const        ri = i; // error: ri was never initialized    }private:    int i;    const int ci;    int &ri;};

Order of Member Initialization

  • Members are initialized in the order in which they appear in the class definition. The order in which initializers appear in the constructor initializer list does not change the order of initialization.
class X{    int i;    int j;public:    // undefined: i is initialized before j    X(int val): j(val), i(j) { }};
  • We should:
    1. Write constructor initializers in the same order as the members are declared.
    2. Write member initializers to use the constructor’s parameters rather than another data member from the same object.

Default Arguments and Constructors

  • A constructor that supplies default arguments for all its parameters also defines the default constructor.
class Sales_data{public:    // defines the default constructor as well as one that takes a string argument    Sales_data(std::string s = ""): bookNo(s) { }    // remaining members as before};

Exercises Section 7.5.1

Exercise 7.36

The following initializer is in error. Identify and fix the problem.

struct X{    X(int i, int j): base(i), rem(base % j) { }    int rem, base;};
struct X{    X(int i, int j): base(i), rem(i % j) { }    int rem, base;};

Exercise 7.37

Using the version of Sales_data from this section, determine which constructor is used to initialize each of the following variables and list the values of the data members in each object:

Sales_data first_item(cin);int main(){    Sales_data next;    Sales_data last("9-999-99999-9");}
  • Sales_data(std::istream &is); value up to input.
  • Sales_data(std::string s = ""); bookNo = “”, cnt = 0, revenue = 0.0
  • Sales_data(std::string s = ""); bookNo = “9-999-99999-9”, cnt = 0, revenue = 0.0

Exercise 7.38

We might want to supply cin as a default argument to the constructor that takes an istream&. Write the constructor declaration that uses cin as a default argument.

  • Sales_data(istream &is = cin);

Exercise 7.39

Would it be legal for both the constructor that takes a string and the one that takes an istream& to have default arguments? If not, why not?

  • No: error: call of overloaded ‘Sales_data()’ is ambiguous

Exercise 7.40

Choose one of the following abstractions(or an abstraction of your own choosing). Determine what data are needed in the class. Provide an appropriate set of constructors. Explain your decisions.
(a) Book
(b) Date
(c) Employee
(d) Vehicle
(e) Object
(f) Tree

#include <iostream>#include <string>class Book{public:    Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pub_date)        :isbn_(isbn), name_(name), author_(author), pub_date_(pub_date) {}    explicit Book(std::istream &in)    {        in >> isbn_ >> name_ >> author_ >> pub_date_;    }private:    unsigned isbn_;    std::string name_;    std::string author_;    std::string pub_date_;};

7.5.2. Delegating Constructors

  • C++11: A delegating constructor uses another constructor from its own class to perform its initialization. A delegating constructor has a member initializer list and a function body. The member initializer list has a single entry that is the name of the class itself which is followed by a parenthesized list of arguments that match another constructor in the class.
class Sales_data{public:    // non-delegating constructor initializes members from corresponding arguments    Sales_data(std::string s, unsigned cnt, double price):        bookNo(s), units_sold(cnt), revenue(cnt*price) {}    // remaining constructors all delegate to another constructor    Sales_data(): Sales_data("", 0, 0) {}    Sales_data(std::string s): Sales_data(s, 0,0) {}    Sales_data(std::istream &is): Sales_data()    {        read(is, *this);    }    // other members as before};
  • The delegated-to constructor’s code would be run before control returned to the function body of the delegating constructor.
    Sales_data(std::istream &is) delegates to the default constructor, which in turn delegates to the three-argument constructor. Once those constructors complete their work, the body of the istream& constructor is run.

Exercises Section 7.5.2

Exercise 7.41

Rewrite your own version of the Sales_data class to use delegating constructors. Add a statement to the body of each of the constructors that prints a message whenever it is executed. Write declarations to construct a Sales_data object in every way possible. Study the output until you are certain you understand the order of execution among delegating constructors.

#include <iostream>#include <string>using std::cin;using std::cout;using std::string;using std::istream;class Sales_data{public:    Sales_data(string s, unsigned cnt, double price) :        bookNo(s), units_sold(cnt), revenue(cnt*price)    {        cout << "Sales_data(string s, unsigned cnt, double price)\n";    }    Sales_data() : Sales_data("", 0, 0)    {        cout << "Sales_data() : Sales_data("", 0, 0)\n";    }    Sales_data(string s) : Sales_data(s, 0,0)    {        cout << "Sales_data(string s) : Sales_data(s, 0,0)\n";    }    Sales_data(istream &is) : Sales_data()    {        cout << "Sales_data(istream &is) : Sales_data()\n";    }private:    string bookNo;    unsigned units_sold = 0;    double revenue = 0.0;};int main(){    cout << "Sales_data obj1:\n";    Sales_data obj1;    cout << "\nSales_data obj2(""gaoxiangnumber1""):\n";    Sales_data obj2("gaoxiangnumber1");    cout << "\nSales_data obj3(cin):\n";    Sales_data obj3(cin);    cout << "\nSales_data obj4(""gao"", 71, 8.8):\n";    Sales_data obj4("gao", 71, 8.8);    return 0;}/*Output:Sales_data obj1:Sales_data(string s, unsigned cnt, double price)Sales_data() : Sales_data(, 0, 0)Sales_data obj2(gaoxiangnumber1):Sales_data(string s, unsigned cnt, double price)Sales_data(string s) : Sales_data(s, 0,0)Sales_data obj3(cin):Sales_data(string s, unsigned cnt, double price)Sales_data() : Sales_data(, 0, 0)Sales_data(istream &is) : Sales_data()Sales_data obj4(gao, 71, 8.8):Sales_data(string s, unsigned cnt, double price)*/

Exercise 7.42

For the class you wrote for ###Exercise 7.40 in §7.5.1(p. 291), decide whether any of the constructors might use delegation. If so, write the delegating constructor(s) for your class. If not, look at the list of abstractions and choose one that you think would use a delegating constructor. Write the class definition for that abstraction.

class Book{public:    Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pub_date)        :isbn_(isbn), name_(name), author_(author), pub_date_(pub_date) {}    explicit Book(std::istream &in)    {        in >> isbn_ >> name_ >> author_ >> pub_date_;    }private:    unsigned isbn_;    std::string name_;    std::string author_;    std::string pub_date_;};

7.5.3. The Role of the Default Constructor

  • The default constructor is used whenever an object is default or value initialized.
    Default initialization happens
    1. When define nonstatic variables(§2.2.1) or arrays(§3.5.1) at block scope without initializers.
    2. When a class that has members of class type uses the synthesized default constructor(§7.1.4).
    3. When members of class type are not explicitly initialized in a constructor initializer list(§7.1.4).
      Value initialization happens
    4. During array initialization when we provide fewer initializers than the size of the array(§3.5.1).
    5. When define a local static object without an initializer(§6.1.1).
    6. When explicitly request value initialization by writing an expressions of the form T() where T is the name of a type(The vector constructor that takes a single argument to specify the vector’s size(§3.3.1) uses an argument of this kind to value initialize its element initializer).
class NoDefault{public:    NoDefault(const std::string&);    // additional members follow, but no other constructors};struct A{    NoDefault my_mem;};A a;            // error: cannot synthesize a constructor for Astruct B{    B() {}  // error: no initializer for b_member    NoDefault b_member;};

Using the Default Constructor

Sales_data obj();   // define a function, not an objectif(obj.isbn() == Primer_5th_ed.isbn())  // error: obj is a functionSales_data obj; // obj is a default-initialized object

Exercises Section 7.5.3

Exercise 7.43

Assume we have a class named NoDefault that has a constructor that takes an int, but has no default constructor. Define a class C that has a member of type NoDefault. Define the default constructor for C.

#include <iostream>class NoDefault{public:    NoDefault(int) {}};class C{public:    C() : no_default_(0) {}private:    NoDefault no_default_;};int main(){    C obj_c;    NoDefault obj_no_default(0);    return 0;}

Exercise 7.44

Is the following declaration legal? If not, why not?
vector<NoDefault> vec(10);

  • No: no default constructor provided.

Exercise 7.45

What if we defined the vector in the previous exercise to hold objects of type C?

  • Ok.

Exercise 7.46

Which, if any, of the following statements are untrue? Why?
(a) A class must provide at least one constructor.
(b) A default constructor is a constructor with an empty parameter list.
(c) If there are no meaningful default values for a class, the class should not provide a default constructor.
(d) If a class does not define a default constructor, the compiler generates one that initializes each data member to the default value of its associated type.

  • a: The compiler can implicitly provide a default constructor.
  • b: It can have default-value parameters.
  • c: It should otherwise may compile error.
  • d: It can’t default initialize class types that don’t have default constructor.

7.5.4. Implicit Class-Type Conversions

  • A constructor that can be called with a single argument defines an implicit conversion from the constructor’s parameter type to the class type. Such constructors are referred to as converting constructors.
string null_book = "9-999-99999-9";// constructs a temporary Sales_data object// with units_sold and revenue equal to 0 and bookNo equal to null_bookitem.combine(null_book);

Only One Class-Type Conversion Is Allowed

  • §4.11.2: The compiler will automatically apply only one class-type conversion, otherwise error.
// error: requires two user-defined conversions:// 1. convert "9-999-99999-9" to string// 2. convert that(temporary) string to Sales_dataitem.combine("9-999-99999-9");// ok: explicit conversion to string, implicit conversion to Sales_dataitem.combine(string("9-999-99999-9"));// ok: implicit conversion to string, explicit conversion to Sales_dataitem.combine(Sales_data("9-999-99999-9"));

Suppressing Implicit Conversions Defined by Constructors

  • We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor as explicit. explicit is meaningful only on constructors that can be called with a single argument and it is used only on the constructor declaration inside the class, not repeated on a definition made outside the class body.
class Sales_data{public:    Sales_data() = default;    Sales_data(const std::string &s, unsigned n, double p):        bookNo(s), units_sold(n), revenue(p*n) { }    explicit Sales_data(const std::string &s): bookNo(s) { }    explicit Sales_data(std::istream&);    // remaining members as before};item.combine(null_book);        // error: string constructor is explicititem.combine(cin);          // error: istream constructor is explicit// error: explicit allowed only on a constructor declaration in a class headerexplicit Sales_data::Sales_data(istream& is){    read(is, *this);}

explicit Constructors Can Be Used Only for Direct Initialization

  • Because implicit conversions happen when we use the copy form of initialization(=, §3.2.1), we cannot use an explicit constructor with copy initialization, must use direct initialization.
Sales_data item1(null_book);  // ok: direct initialization// error: cannot use the copy form of initialization with an explicit constructorSales_data item2 = null_book;

Explicitly Using Constructors for Conversions

// ok: the argument is an explicitly constructed Sales_data objectitem.combine(Sales_data(null_book));// ok: static_cast can use an explicit constructoritem.combine(static_cast<Sales_data>(cin));

Library Classes with explicit Constructors

  • Some library classes have single-parameter constructors:
    1. The string constructor that takes a single const char* parameter is not explicit.
    2. The vector constructor that takes a size is explicit.

Exercises Section 7.5.4

Exercise 7.47

Explain whether the Sales_data constructor that takes a string should be explicit. What are the benefits of making the constructor explicit? What are the drawbacks?

  • Whether the conversion of a string to Sales_data is desired depends on how we think our users will use the conversion.
  • Benefits: prevent the use of a constructor in a context that requires an implicit conversion.
  • Drawbacks: meaningful only on constructors that can be called with a single argument.

Exercise 7.48

Assuming the Sales_data constructors are not explicit, what operations happen during the following definitions

string null_isbn("9-999-99999-9");Sales_data item1(null_isbn);Sales_data item2("9-999-99999-9");

What happens if the Sales_data constructors are explicit?

  • Both are nothing happened.

Exercise 7.49

For each of the three following declarations of combine, explain what happens if we call i.combine(s), where i is a Sales_data and s is a string:
(a) Sales_data &combine(Sales_data);
(b) Sales_data &combine(Sales_data&);
(c) Sales_data &combine(const Sales_data&) const;

  • ok. s converts to a temporary object.
  • error: no matching function for call to ‘Sales_data::combine(std::string&)’
  • error: the last const should not appear since it forbids any modification on data members.

Exercise 7.50

Determine whether any of your Person class constructors should be explicit.

  • explicit Person(std::istream& is) { read(is, *this); }

Exercise 7.51

Why do you think vector defines its single-argument constructor as explicit, but string does not?

  • int Fun1(const std::vector<int>&);
    If vector has not defined its single-argument constructor as explicit, we can use the function like:
    Fun1(34);
    This is very confused.
  • void Fun2(std::string);
    In ordinary, we use std::string to replace const char *, when we call a function:
    Fun2("gaoxiangnumber1");
    It is very natural.

7.5.5. Aggregate Classes

  • A class is an aggregate if
    1. All data members are public.
    2. Not define any constructors.
    3. No in-class initializers(§2.6.1, p. 73).
    4. No base classes or virtual functions(Chapter 15).
  • We can initialize the data members by providing a braced list of member initializers that must appear in declaration order of the data members. If the list of initializers has fewer elements than the data members, the trailing members are value initialized(§3.5.1). The list of initializers must not contain more elements than the class has members.
// Example:struct Data{    int ival;    string s;};Data val1 = { 0, "Anna" };  // val1.ival = 0; val1.s = string("Anna")

Exercises Section 7.5.5

Exercise 7.52

Using our first version of Sales_data from §2.6.1(p. 72), explain the following initialization. Identify and fix any problems.

Sales_data item = {"978-0590353403", 25, 15.99};

struct Sales_data{    std::string bookNo;    unsigned units_sold;    double revenue;};

7.5.6. Literal Classes

  • Literal types include: the arithmetic types, references, pointers, and certain classes. Literal classes may have function members that are constexpr and these member functions are implicitly const(§7.1.2).
  • An aggregate class(§7.5.5) whose data members are all literal type is a literal class. A nonaggregate class that meets the following restrictions is also a literal class:
    1. The data members all are literal types.
    2. The class must have at least one constexpr constructor.
    3. If a data member has an in-class initializer, the initializer for a member of built-in type must be a constant expression(§2.4.4), or if the member has class type, the initializer must use the member’s own constexpr constructor.
    4. The class must use default definition for its destructor, which is the member that destroys objects of the class type(§7.1.5).

constexpr Constructors

  • Though constructors can’t be const(§7.1.4), constructors in a literal class can be constexpr(§6.5.2) functions. A literal class must provide at least one constexpr constructor.
  • A constexpr constructor can be declared as = default(§7.1.4) or as a deleted function(§13.1.6(p. 507)); otherwise, it must meet the requirements of
    1. a constructor: it has no return statement;
    2. a constexpr function: the only executable statement it can have is a return statement(§6.5.2).
      So, the body of a constexpr constructor is typically empty. We define a constexpr constructor by preceding its declaration with the keyword constexpr:
class Debug{public:    constexpr Debug(bool b = true): hardware(b), io(b), other(b) {}    constexpr Debug(bool h, bool i, bool o) : hardware(h), io(i), other(o) {}    constexpr bool any()    {        return hardware || io || other;    }    void set_io(bool b)    {        io = b;    }    void set_hardware(bool b)    {        hardware = b;    }    void set_other(bool b)    {        hardware = b;    }private:    bool hardware;  // hardware errors other than IO errors    bool io;            // IO errors    bool other;     // other errors};
  • A constexpr constructor must initialize every data member. The initializers must either use a constexpr constructor or be a constant expression. A constexpr constructor is used to generate objects that are constexpr and for parameters or return types in constexpr functions:
constexpr Debug io_sub(false, true, false); // debugging IOif(io_sub.any())                            // equivalent to if(true)    cerr << "print appropriate error messages" << endl;constexpr Debug prod(false);                // no debugging during productionif(prod.any())                          // equivalent to if(false)    cerr << "print an error message" << endl;

Exercises Section 7.5.6

Exercise 7.53

Define your own version of Debug.

#include <iostream>using std::cout;class Debug{public:    constexpr Debug(bool b = true): hardware(b), io(b), other(b) {}    constexpr Debug(bool h, bool i, bool o) : hardware(h), io(i), other(o) {}    constexpr bool any()    {        return hardware || io || other;    }    void set_io(bool b)    {        io = b;    }    void set_hw(bool b)    {        hardware = b;    }    void set_other(bool b)    {        hardware = b;    }private:    bool hardware;// hardware errors other than IO errors    bool io;// IO errors    bool other;// other errors};int main(){    constexpr Debug io_sub(false, true, false); // debugging IO    if (io_sub.any()) // equivalent to if(true)        cout << "print appropriate error messages" << '\n';    constexpr Debug prod(false); // no debugging during production    if (prod.any()) // equivalent to if(false)        cout << "print an error message" << '\n';    return 0;}

Exercise 7.54

Should the members of Debug that begin with set_ be declared as constexpr? If not, why not?

  • C++11: constexpr member functions are implicitly const, so can’t.

Exercise 7.55

Is the Data class from §7.5.5(p. 298) a literal class? If not, why not? If so, explain why it is literal.

  • No: std::string is not a literal type.

7.6. static Class Members

  • static member is associated with the class ad it can be public or private. The type of a static data member can be const, reference, array, class type, and so forth.
class Account{public:    void calculate()    {        amount += amount * interestRate;    }    static double rate()    {        return interestRate;    }    static void rate(double);private:    std::string owner;    double amount;    static double interestRate;    static double initRate();};
  • The static members of a class exist outside any object. Each Account object contains two data members(owner and amount). There is only one interestRate object that will be shared by all the Account objects.
  • static member functions are not bound to any object and they don’t have a this pointer. They may not be declared as const and we may not either explicit use of this or -implicit uses of this by calling a nonstatic member in the body of a static member

Using a Class static Member

  • We can access a static member by the scope operator; or use an object, reference, or pointer of the class type.
double r = Account::rate(); // access a static member using the scope operatorAccount ac1;Account *ac2 = &ac1;r = ac1.rate();             // through an Account object or referencer = ac2->rate();                // through a pointer to an Account object
  • Member functions can use static members without the scope operator.
class Account{public:    void calculate()    {        amount += amount * interestRate;    }private:    static double interestRate;    // remaining members as before};

Defining static Members

  • We can define a static member function inside or outside of the class body. When we define a static member outside the class, we don’t repeat the static keyword.
void Account::rate(double newRate){    interestRate = newRate;}
  • Because static data members are not part of individual objects of the class type, they are not initialized by the class’ constructors. We must define and initialize each static data member outside the class body and any function. A static data member may be defined only once. We name the object’s type, followed by the name of the class, the scope operator, and the member’s own name.
// define and initialize a static class memberdouble Account::interestRate = initRate();
  • Once the class name is seen, the remainder of the definition is in the scope of the class, so, we can use initRate() without Account::.

In-Class Initialization of static Data Members

  • We can provide in-class initializers for static members that have const integral type and must do so for static members that are constexprs of literal type(§7.5.6). The initializers must be constant expressions. Such members are themselves constant expressions.
class Account{public:    static double rate()    {        return interestRate;    }    static void rate(double);private:    static constexpr int period = 30;// period is a constant expression    double daily_tbl[period];};
  • If the member is used only in contexts where the compiler can substitute the member’s value, then an initialized const or constexpr static need not be separately defined. If we use the member in a context in which the value cannot be substituted, then there must be a definition for that member. E.g., if the only use of period is to define the dimension of daily_tbl, there is no need to define period outside of Account; if we pass Account::period to a function that takes a const int&, then period must be defined.
  • If an initializer is provided inside the class, the member’s definition must not specify an initial value:
// definition of a static member with no initializerconstexpr int Account::period;  // initializer provided in the class definition

static Members Can Be Used in Ways Ordinary Members Can’t

  • static data members can have incomplete type(§7.3.3): They can have the same type as the class type of which it is a member, a nonstatic data member is restricted to being declared as a pointer or a reference to an object of its class.
class Bar{private:    static Bar mem1;    // ok: static member can have incomplete type    Bar *mem2;      // ok: pointer member can have incomplete type    Bar mem3;       // error: data members must have complete type};
  • A static member can be used as a default argument(§6.5.1), a nonstatic data member can’t because its value is part of the object of which it is a member.
class Screen{public:    // bkground refers to the static member    // declared later in the class definition    Screen& clear(char = bkground);private:    static const char bkground;};

Exercises Section 7.6

Exercise 7.56

What is a static class member? What are the advantages of static members? How do they differ from ordinary members?

  • A class member that is associated with the class, rather than with individual objects of the class type.
  • Each object can no need to store a common data. And if the data is changed, each object can use the new value.
    1. A static data member can have incomplete type.
    2. We can use a static member as a default argument.

Exercise 7.57

Write your own version of the Account class.

#include <string>using std::string;class Account{public:    void calculate()    {        amount += amount * interestRate;    }    static double rate()    {        return interestRate;    }    static void rate(double newRate)    {        interestRate = newRate;    }private:    std::string owner;    double amount;    static double interestRate;    static constexpr double today_rate = 71.88;    static double initRate()    {        return today_rate;    }};double Account::interestRate = initRate();

Exercise 7.58

Which, if any, of the following static data member declarations and definitions are errors? Explain why.

// example.hclass Example{public:    static double rate = 6.5;    static const int vecSize = 20;    static vector<double> vec(vecSize);};// example.C#include "example.h"double Example::rate;vector<double> Example::vec;
// example.hclass Example{public:    static constexpr double rate = 6.5; //rate should be a constant expression    static const int vecSize = 20;    static vector<double> vec;};// example.C#include "example.h"constexpr double Example::rate;//we should specify an inclass initializervector<double> Example::vec(Example::vecSize);

Chapter Summary

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

0 0
原创粉丝点击