13-Copy Control

来源:互联网 发布:windows截图保存在哪里 编辑:程序博客网 时间:2024/06/06 16:31

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

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

  • A class controls copied, moved, assigned, and destroyed operations by defining five member functions: copy constructor, copy-assignment operator, move constructor, move-assignment operator, and destructor.
    1. The copy and move constructors define what happens when an object is initialized from another object of the same type.
    2. The copy- and move-assignment operators define what happens when we assign an object of a class type to another object of that same class type.
    3. The destructor defines what happens when an object of the type ceases to exist.
  • If a class does not define all of the copy-control members, the compiler automatically defines the missing operations. But for some classes, relying on the default definitions leads to disaster.

13.1. Copy, Assign, and Destroy

13.1.1. The Copy Constructor

  • A constructor is the copy constructor if its first parameter is a reference to the class type(often a reference to const) and any additional parameters have default values. Since the copy constructor is usually used implicitly, it should not be explicit(§7.5.4).
class Foo{public:    Foo();              // default constructor    Foo(const Foo&);    // copy constructor    // ...};

The Synthesized Copy Constructor

  • When we don’t define a copy constructor for a class, the compiler synthesizes one for us. Unlike the synthesized default constructor(§7.1.4), a copy constructor is synthesized even if we define other constructors.
  • The synthesized copy constructor for some classes prevents us from copying objects of that class type(§13.1.6); otherwise, it copies each nonstatic member in turn from the given object into the one being created. The type of each member determines how that member is copied:
    1. Members of class type are copied by the copy constructor for that class.
    2. Members of built-in type are copied directly. Since array can’t be directly copied, it is copied by copying each element.
class Sales_data{public:    // equivalent to the synthesized copy constructor    Sales_data(const Sales_data &orig) :        bookNo(orig.bookNo),        // uses the string copy constructor        units_sold(orig.units_sold),    // copies orig.units_sold        revenue(orig.revenue)       // copies orig.revenue    { }                             // empty bodyprivate:    std::string bookNo;    int units_sold = 0;    double revenue = 0.0;};

Copy Initialization

  • Differences between direct initialization and copy initialization(§3.2.1):
    1. When we use direct initialization, we ask the compiler to select the constructor that best matches the arguments we provide.
    2. When we use copy initialization, we ask the compiler to copy the right-hand operand into the object being created, converting that operand if necessary(§7.5.4).
  • Copy initialization ordinarily uses the copy constructor; but if a class has a move constructor, then copy initialization sometimes uses the move constructor instead of the copy constructor(§13.6.2).
  • Copy initialization happens:
    1. When we define variables using =.
    2. Pass an object as an argument to a parameter of nonreference type.
    3. Return an object from a function that has a nonreference return type.
    4. Brace initialize the elements in an array or the members of an aggregate class (§7.5.5).
string a(10, '.');          // direct initializationstring b(a);                // direct initializationstring c = b;               // copy initializationstring d = "xiang";     // copy initializationstring e = string(9, '0');  // copy initialization
  • The fact that the copy constructor is used to initialize nonreference parameters of class type explains why the copy constructor’s own parameter must be a reference.
    If that parameter were not a reference, then to call the copy constructor, we’d need to use the copy constructor to copy the argument, but to copy the argument, we’d need to call the copy constructor, and so on indefinitely.
  • Some class types use copy initialization for the objects they allocate. E.g., the library containers copy initialize their elements when we initialize the container, or when we call an insert or push member(§9.3.1). By contrast, elements created by an emplace member are direct initialized(§9.3.1).

Constraints on Copy Initialization

  • Whether we use copy or direct initialization matters if we use an initializer that requires conversion by an explicit constructor(§7.5.4).
vector<int> v1(10); // ok: direct initializationvector<int> v2 = 10;    // error: constructor that takes a size is explicitvoid f(vector<int>);    // f's parameter is copy initializedf(10);              // error: can't use an explicit constructor to copy an argumentf(vector<int>(10)); // ok: directly construct a temporary vector from an int
  • Copy initialization of v2 is an error because the vector constructor that takes a single size parameter is explicit. If we want to use an explicit constructor, we must do so explicitly(the last line of example).

The Compiler Can Bypass the Copy Constructor

  • During copy initialization, the compiler is permitted to skip the copy/move constructor and create the object directly. That is, the compiler is permitted to rewrite
    string null_book = "9-999-99999-9"; // copy initialization
    into
    string null_book("9-999-99999-9"); // compiler omits the copy constructor
  • Even if the compiler omits the call to the copy/move constructor, the copy/move constructor must exist and must be accessible(e.g., not private) at that point in the program.

Exercises Section 13.1.1

Exercise 13.1

What is a copy constructor? When is it used?

  • A constructor is the copy constructor if its first parameter is a reference to the class type(often a reference to const) and any additional parameters have default values.
  • Copy initialization happens:
    1. When we define variables using =.
    2. Pass an object as an argument to a parameter of nonreference type.
    3. Return an object from a function that has a nonreference return type.
    4. Brace initialize the elements in an array or the members of an aggregate class (§7.5.5).

Exercise 13.2

Explain why the following declaration is illegal:

Sales_data::Sales_data(Sales_data rhs);
  • The first parameter is a reference to the class type.

Exercise 13.3

What happens when we copy a StrBlob? What about StrBlobPtrs?

StrBlob(const StrBlob &obj) : id_(obj.id_), data(obj.data) {}StrBlobPtr(const StrBlobPtr &obj) : wptr(obj.wptr), curr(obj.curr) {}

Exercise 13.4

Assuming Point is a class type with a public copy constructor, identify each use of the copy constructor in this program fragment:

Point global;Point foo_bar(Point arg){    Point local = arg, *heap = new Point(global);    *heap = local;    Point pa[ 4 ] = { local, *heap };    return *heap;}
#include <iostream>using namespace std;class Point{public:    Point(string id) : id_(id)    {        cout << "Constructor: id = " << id_ << "\n";    }    Point(const Point &obj) : id_(obj.id_)    {        cout << "Copy constructor: id = " << id_ << "\n";    }    ~Point()    {        cout << "Destructor: id = " << id_ << "\n";    }private:    string id_;};Point global("global");Point foo_bar(Point arg){    cout << "Enter call, before `Point local = arg;`\n";    Point local = arg;    cout << "After `Point local = arg;`, before `Point *heap = new Point(global);`\n";    Point *heap = new Point(global);    cout << "After `Point *heap = new Point(global);`, before `*heap = local;`\n";    *heap = local;    cout << "After `*heap = local;`, before `Point pa[ 4 ] = { local, *heap };`\n";    Point pa[ 4 ] = {local, *heap, string("default 1"), string("default 2")};    cout << "After `Point pa[ 4 ] = { local, *heap };`, before `return *heap;`\n";    return *heap;}int main(){    Point obj("obj");    cout << "Before call\n";    foo_bar(obj);    cout << "After call\n";}/*Output:Constructor: id = globalConstructor: id = objBefore callCopy constructor: id = objEnter call, before `Point local = arg;`Copy constructor: id = objAfter `Point local = arg;`, before `Point *heap = new Point(global);`Copy constructor: id = globalAfter `Point *heap = new Point(global);`, before `*heap = local;`After `*heap = local;`, before `Point pa[ 4 ] = { local, *heap };`Copy constructor: id = objCopy constructor: id = objConstructor: id = default 1Constructor: id = default 2After `Point pa[ 4 ] = { local, *heap };`, before `return *heap;`Copy constructor: id = objDestructor: id = default 2Destructor: id = default 1Destructor: id = objDestructor: id = objDestructor: id = objDestructor: id = objDestructor: id = objAfter callDestructor: id = objDestructor: id = global*/

Exercise 13.5

Given the following sketch of a class, write a copy constructor that copies all the members. Your constructor should dynamically allocate a new string(§12.1.2) and copy the object to which ps points, rather than copying ps itself.

class HasPtr{public:    HasPtr(const std::string &s = std::string()): ps(new std::string(s)), i(0) {}private:    std::string *ps;    int i;};
HasPtr(const HasPtr &obj) : ps(new std::string(*(obj.ps))), i(obj.i) {}

13.1.2. The Copy-Assignment Operator

Introducing Overloaded Assignment

  • Overloaded operators are functions that have the name operator followed by the symbol for the operator being defined. The assignment operator is a function named operator=. An operator function has a return type and a parameter list.
  • The parameters represent the operands of the operator. Some operators(includes assignment) must be defined as member functions. When an operator is a member function, the left-hand operand is bound to the implicit this parameter(§7.1.2). The right-hand operand in a binary operator is passed as an explicit parameter. The copy-assignment operator takes an argument of the same type as the class.
class Foo{public:    Foo& operator=(const Foo&); // assignment operator    // ...};
  • To be consistent with assignment for the built-in types(§4.4), assignment operators usually return a reference to their left-hand operand. The library generally requires that types stored in a container have assignment operators that return a reference to the left-hand operand.

The Synthesized Copy-Assignment Operator

  • The compiler generates a synthesized copy-assignment operator for a class if the class does not define its own. For some classes the synthesized copy-assignment operator disallows assignment(§13.1.6); otherwise, it assigns each nonstatic member of the right-hand object to the corresponding member of the left-hand object using the copy-assignment operator for the type of that member. Array members are assigned by assigning each element of the array. The synthesized copy-assignment operator returns a reference to its left-hand object.
// equivalent to the synthesized copy-assignment operatorSales_data& Sales_data::operator=(const Sales_data &rhs){    bookNo = rhs.bookNo;        // calls the string::operator=    units_sold = rhs.units_sold;    // uses the built-in int assignment    revenue = rhs.revenue;      // uses the built-in double assignment    return *this;                   // return a reference to this object}

Exercises Section 13.1.2

Exercise 13.6

What is a copy-assignment operator? When is this operator used? What does the synthesized copy-assignment operator do? When is it synthesized?

  • Type &operator=(const Type &rhs);
  • Object obj2 = obj1;
  • Assigns each nonstatic member of the right-hand object to the corresponding member of the left-hand object using the copy-assignment operator for the type of that member.
  • When we don’t define it ourself.

Exercise 13.7

What happens when we assign one StrBlob to another? What about StrBlobPtrs?

StrBlob &operator=(const StrBlob &rhs){    id_ = rhs.id_;    data = rhs.data;    return *this;}StrBlobPtr &operator=(const StrBlobPtr &rhs){    wptr = rhs.wptr;    curr = rhs.curr;    return *this;}

Exercise 13.8

Write the assignment operator for the HasPtr class from ###Exercise 13.5 in §13.1.1(p. 499). As with the copy constructor, your assignment operator should copy the object to which ps points.

class HasPtr{public:    HasPtr(const string &s = string()) : ps(new string(s)), i(0) {}    HasPtr(const HasPtr &orig) : ps(new string(*(orig.ps))), i(orig.i) {}    HasPtr &operator=(const HasPtr &rhs)    {        ps = new string(*(rhs.ps));        i = rhs.i;    }private:    string *ps;    int i;};

13.1.3. The Destructor

  • Constructors initialize the nonstatic data members of an object and may do other work; destructors do whatever work is needed to free the resources used by an object and destroy the nonstatic data members of the object.
  • The destructor is a member function with the name of the class prefixed by a tilde(~). It has no return value and takes no parameters. Because it takes no parameters, it can’t be overloaded. There is always only one destructor for a given class.
class Foo{public:    ~Foo(); // destructor    // ...};

What a Destructor Does

  • As a constructor has an initialization part and a function body(§7.5.1), a destructor has a function body and a destruction part.
    1. In a constructor, members are initialized in the same order as they appear in the class before the function body is executed.
    2. In a destructor, the function body is executed first and then the members are destroyed in reverse order from the order in which they were initialized.
  • The function body of a destructor does whatever operations the class designer wishes to do. Typically, the destructor frees resources an object allocated during its lifetime.
  • In a destructor, the destruction part is implicit. What happens when a member is destroyed depends on the type of the member.
    1. Members of class type are destroyed by running the member’s own destructor.
    2. The built-in types don’t have destructors, so nothing is done to destroy members of built-in type.
  • Note that the destruction of a member of built-in pointer type does not delete the object to which that pointer points. Because the smart pointers(§12.1.1) are class types and have destructors, members that are smart pointers are automatically destroyed during the destruction phase.

When a Destructor Is Called

  • The destructor is used automatically whenever an object of its type is destroyed:
    • Variables are destroyed when they go out of scope.
    • Members of an object are destroyed when the object of which they are a part is destroyed.
    • Elements in a container(a library container or an array) are destroyed when the container is destroyed.
    • Dynamically allocated objects are destroyed when the delete operator is applied to a pointer to the object(§12.1.2).
    • Temporary objects are destroyed at the end of the full expression in which the temporary was created.
  • The destructor isn’t run when a reference or a pointer to an object goes out of scope.

The Synthesized Destructor

  • The compiler defines a synthesized destructor for any class that does not define its own destructor. For some classes, the synthesized destructor is defined to disallow objects of the type from being destroyed(§13.1.6); otherwise, the synthesized destructor has an empty function body. E.g., the synthesized Sales_data destructor is equivalent to:
class Sales_data{public:    // no work to do other than destroying the members, which happens automatically    ~Sales_data() {}    // other members as before};
  • The destructor body does not directly destroy the members themselves. Members are destroyed as part of the implicit destruction phase that follows the destructor body. A destructor body executes in addition to the memberwise destruction that takes place as part of destroying an object.

Exercises Section 13.1.3

Exercise 13.9

What is a destructor? What does the synthesized destructor do? When is a destructor synthesized?

  • Destructors do whatever work is needed to free the resources used by an object and destroy the nonstatic data members of the object.
  • For some classes, the synthesized destructor is defined to disallow objects of the type from being destroyed(§13.1.6); otherwise, the synthesized destructor has an empty function body.
  • When we don’t define it ourself.

Exercise 13.10

What happens when a StrBlob object is destroyed? What about a StrBlobPtr?

  • When a StrBlob object destroyed, the use_count of the dynamic object will decrement. It will be freed if no shared_ptr to that dynamic object.
  • When a StrBlobPtr object is destroyed the object dynamically allocated will not be freed.

Exercise 13.11

Add a destructor to your HasPtr class from the previous exercises.

class HasPtr{public:    HasPtr(const string &s = string()) : ps(new string(s)), i(0) {}    HasPtr(const HasPtr &orig) : ps(new string(*(orig.ps))), i(orig.i) {}    HasPtr &operator=(const HasPtr &rhs)    {        ps = new string(*(rhs.ps));        i = rhs.i;    }    ~HasPtr()    {        delete ps;    }private:    string *ps;    int i;};

Exercise 13.12

How many destructor calls occur in the following code fragment?

bool fcn(const Sales_data *trans, Sales_data accum){    Sales_data item1(*trans), item2(accum);    return item1.isbn() != item2.isbn();}
  • Three times: accum, item1 and item2.

Exercise 13.13

A good way to understand copy-control members and constructors is to define a simple class with these members in which each member prints its name:

struct X{    X()    {        std::cout << "X()" << std::endl;    }    X(const X&)    {        std::cout << "X(const X&)" << std::endl;    }};

Add the copy-assignment operator and destructor to X and write a program using X objects in various ways: Pass them as nonreference and reference parameters; dynamically allocate them; put them in containers; and so forth. Study the output until you are certain you understand when and why each copy-control member is used. As you read the output, remember that the compiler can omit calls to the copy constructor.

#include <iostream>#include <vector>using namespace std;struct X{    X(string id) : id_(id)    {        cout << "X(): id = " << id_ << "\n";    }    X(const X &orig) : id_(orig.id_)    {        cout << "X(const X&): id = " << id_ << "\n";    }    X &operator=(const X &)    {        cout << "X &operator=(const X &): id = " << id_ << "\n";        return *this;    }    ~X()    {        cout << "~X(): id = " << id_ << "\n";    }    string id_;};void Fun1(X obj){    cout << "Fun1(X): id = " << obj.id_ << "\n";}void Fun2(X &obj){    cout << "Fun2(X&): id = " << obj.id_ << "\n";}int main(){    X obj1("obj1");    Fun1(obj1);    Fun2(obj1);    X *obj2 = new X("obj2");    vector<X> vec(5, string("obj3"));    delete obj2;    cout << "EXIT\n";    return 0;}/*Output:X(): id = obj1X(const X&): id = obj1Fun1(X): id = obj1~X(): id = obj1Fun2(X&): id = obj1X(): id = obj2X(): id = obj3X(const X&): id = obj3X(const X&): id = obj3X(const X&): id = obj3X(const X&): id = obj3X(const X&): id = obj3~X(): id = obj3~X(): id = obj2EXIT~X(): id = obj3~X(): id = obj3~X(): id = obj3~X(): id = obj3~X(): id = obj3~X(): id = obj1*/

13.1.4. The Rule of Three/Five

  • There are three basic operations to control copies of class objects: the copy constructor, copy-assignment operator, and destructor. Under C++11, a class can define a move constructor and move-assignment operator(§13.6).

Classes That Need Destructors Need Copy and Assignment

  • If the class needs a destructor, it also needs a copy constructor and copy-assignment operator.
  • Since the HasPtr class(§13.1.1) allocates dynamic memory in its constructor and the synthesized destructor will not delete a data member that is a pointer, it needs to define a destructor to free the memory allocated by its constructor.
  • Consider what would happen if we gave HasPtr a destructor but used the synthesized versions of the copy constructor and copy-assignment operator.
class HasPtr{public:    HasPtr(const std::string &s = std::string()): ps(new std::string(s)), i(0) {}    ~HasPtr()    {        delete ps;    }    // WRONG: HasPtr needs a copy constructor and copy-assignment operator    // other members as before};
  • The synthesized versions of copy and assignment copy the pointer member, meaning that multiple HasPtr objects may be pointing to the same memory.
HasPtr f(HasPtr hp) // HasPtr passed by value, so it is copied{    HasPtr ret = hp;    // copies the given HasPtr    // process ret    return ret;     // ret and hp are destroyed}
  • When f returns, both hp and ret are destroyed and the HasPtr destructor is run on each of these objects. That destructor will delete the pointer member in ret and in hp. Since these objects contain the same pointer value, this code will delete that pointer twice, which is an error. The caller of f may still use the object that was passed to f.
HasPtr p("some values");f(p);       // when f completes, the memory to which p.ps points is freedHasPtr q(p);    // now both p and q point to invalid memory

Classes That Need Copy Need Assignment, and Vice Versa

  • Some classes have work that needs to be done to copy or assign objects but has no need for the destructor.
    Consider a class that gives each object a unique serial number. This class needs copy constructor/copy-assignment operator that copies/assigns all data members except serial number for the object being created. But this class doesn’t need a destructor.
  • If a class needs a copy constructor, it also needs a copy-assignment operator, and vice versa. But needing either the copy constructor or the copy-assignment operator does not indicate the need for a destructor.

Exercises Section 13.1.4

Exercise 13.14

Assume that numbered is a class with a default constructor that generates a unique serial number for each object, which is stored in a data member named my_serial_number. Assuming numbered uses the synthesized copy-control members and given the following function:

void f(numbered s){    cout << s.my_serial_number << endl;}

what output does the following code produce?

numbered a, b = a, c = b;f(a);f(b);f(c);
  • Three same numbers.

Exercise 13.15

Assume numbered has a copy constructor that generates a new serial number. Does that change the output of the calls in the previous exercise? If so, why? What output gets generated?

  • Three distinct numbers.

Exercise 13.16

What if the parameter in f were const numbered&? Does that change the output? If so, why? What output gets generated?

  • The output will change. Because no copy operation happens within function f, the three output are the same.

Exercise 13.17

Write versions of numbered and f corresponding to the previous three exercises and check whether you correctly predicted the output.

#include <iostream>#include <vector>using namespace std;int cnt = 0;class numbered1{    friend void f1(numbered1);public:    numbered1()    {        num = ++cnt;        cout << "numbered1(): cnt = " << cnt << '\n';    }private:    int num;};void f1(numbered1 s){    cout << s.num << '\n';}class numbered2{    friend void f2(numbered2);    friend void f3(const numbered2 &);public:    numbered2()    {        num = ++cnt;        cout << "numbered2(): cnt = " << cnt << '\n';    }    numbered2(const numbered2 &orig)    {        cout << "numbered2(const numbered2 &): old num = " << num;        num = ++cnt;        cout << ", new num = " << cnt << '\n';    }private:    int num;};void f2(numbered2 s){    cout << s.num << '\n';}void f3(const numbered2 &s){    cout << s.num << '\n';}int main(){    numbered1 a1, b1 = a1, c1 = b1;    f1(a1);    f1(b1);    f1(c1);    cout << "###" << cnt << "###\n";    numbered2 a2, b2 = a2, c2 = b2;    f2(a2);    f2(b2);    f2(c2);    cout << "###" << cnt << "###\n";    f3(a2);    f3(b2);    f3(c2);    cout << "###" << cnt << "###\n";    return 0;}/*Output:numbered1(): cnt = 1111###1###numbered2(): cnt = 2numbered2(const numbered2 &): old num = 2, new num = 3numbered2(const numbered2 &): old num = 1, new num = 4numbered2(const numbered2 &): old num = 4197328, new num = 55numbered2(const numbered2 &): old num = 5, new num = 66numbered2(const numbered2 &): old num = 6, new num = 77###7###234###7###*/

13.1.5. Using = default

  • We can explicitly ask the compiler to generate the synthesized versions of the copy-control members by defining them as = default(§7.1.4).
class Sales_data{public:    // copy control; use defaults    Sales_data() = default;    Sales_data(const Sales_data&) = default;    Sales_data& operator=(const Sales_data &);    ~Sales_data() = default;    // other members as before};Sales_data& Sales_data::operator=(const Sales_data&) = default;
  • When we specify = default on the declaration of the member inside the class body, the synthesized function is implicitly inline(as is any other member function defined in the body of the class). If we don’t want the synthesized member to be an inline function, we can specify = default on the member’s definition.
  • We can use = default only on member functions that have a synthesized version(i.e., the default constructor or a copy-control member).

13.1.6. Preventing Copies

  • Some classes must be defined so as to prevent copies or assignments from being made. E.g., the iostream classes prevent copying to avoid letting multiple objects write to or read from the same IO buffer.

Defining a Function as Deleted

  • Under C++11, we can prevent copies by defining the copy constructor and copy-assignment operator as deleted functions that are declared but may not be used in any other way. = delete must appear on the first declaration of a deleted function.
struct NoCopy{    NoCopy() = default;    NoCopy(const NoCopy&) = delete;         // no copy    NoCopy &operator=(const NoCopy&) = delete;  // no assignment    ~NoCopy() = default;    // other members};
  • Unlike = default(we can use = default only on the default constructor or a copy-control member that the compiler can synthesize), we can specify = delete on any function.

The Destructor Should Not be a Deleted Member

  • If the destructor is deleted, then there is no way to destroy objects of that type. The compiler will not let us define variables or create temporaries of a type that has a deleted destructor.
  • We can’t define variables or temporaries of a class that has a member whose type has a deleted destructor. If a member has a deleted destructor, then that member can’t be destroyed; if a member can’t be destroyed, the object as a whole can’t be destroyed.
  • We can dynamically allocate objects with a deleted destructor, but we can’t free them.
struct NoDtor{    NoDtor() = default;    ~NoDtor() = delete;};NoDtor nd;                  // error: use of deleted function ‘NoDtor::~NoDtor()’NoDtor *p = new NoDtor();   // ok: but we can't delete pdelete p;                       // error: use of deleted function ‘NoDtor::~NoDtor()’

The Copy-Control Members May Be Synthesized as Deleted

  • The compiler defines synthesized members as deleted functions.
    • The synthesized destructor is defined as deleted if the class has a member whose own destructor is deleted or is inaccessible(e.g., private).
    • The synthesized copy constructor is defined as deleted if the class has a member whose own copy constructor or destructor is deleted or inaccessible.
    • The synthesized copy-assignment operator is defined as deleted if a member has a deleted or inaccessible copy-assignment operator, or if the class has a const or reference member.
    • The synthesized default constructor is defined as deleted if the class has a member with a deleted or inaccessible destructor; or has a reference member that does not have an in-class initializer(§2.6.1); or has a const member whose type does not explicitly define a default constructor and that member does not have an in-class initializer.
  • In essence, these rules mean that if a class has a data member that can’t be default constructed, copied, assigned, or destroyed, then the corresponding member will be a deleted function.
  • The reason for the rule that a member which has a deleted or inaccessible destructor causes the synthesized default and copy constructors to be defined as deleted is that without it, we could create objects that we could not destroy.
  • The compiler will not synthesize a default constructor for a class with a reference member or a const member that can’t be default constructed.
    A class with a const member can’t use the synthesized copy-assignment operator: this operator attempts to assign to every member, it isn’t possible to assign a new value to a const object.
  • Although we can assign a new value to a reference, doing so changes the value of the object to which the reference refers. If the copy-assignment operator were synthesized for such classes, the left-hand operand would continue to refer to the same object as it did before the assignment, not refer to the same object as the right-hand operand. So, the synthesized copy-assignment operator is defined as deleted if the class has a reference member.
  • In §13.6.2, §15.7.2, and §19.6, there are other aspects of a class that can cause its copy members to be defined as deleted.

private Copy Control

  • Prior to C++11, classes prevent copies by declaring their copy constructor and copy-assignment operator as private and don’t define them.
    1. Because of private, user code that tries to make a copy will be flagged as an error at compile time; but friends and members of the class can still make copies.
    2. With one exception in §15.2.1, it is legal to declare but not define a member function. An attempt to use an undefined member results in a link-time failure, so, copies made in member functions or friends will result in an error at link time.
class PrivateCopy{    PrivateCopy(const PrivateCopy&);    PrivateCopy &operator=(const PrivateCopy&);    // other memberspublic:    PrivateCopy() = default;    // use the synthesized default constructor    ~PrivateCopy();            // users can define objects of this type but not copy them};
  • With C++11, classes that want to prevent copying should define their copy constructor and copy-assignment operators using = delete rather than making those members private and undefined.

Exercises Section 13.1.6

Exercise 13.18

Define an Employee class that contains an employee name and a unique employee identifier. Give the class a default constructor and a constructor that takes a string representing the employee’s name. Each constructor should generate a unique ID by incrementing a static data member.

#include <iostream>#include <string>using namespace std;class Employee{public:    Employee() : name_(""), id_(++static_id)    {        cout << "Employee(): id = " << id_ << '\n';    }    Employee(string name) : name_(name), id_(++static_id)    {        cout << "Employee(string name): name = " << name_             << ", id = " << id_ << '\n';    }private:    string name_;    int id_;    static int static_id;};int Employee::static_id = 0;int main(){    Employee obj1;    Employee obj2("gaoxiang");    return 0;}/*Output:Employee(): id = 1Employee(string name): name = gaoxiang, id = 2*/

Exercise 13.19

Does your Employee class need to define its own versions of the copy-control members? If so, why? If not, why not? Implement whatever copy-control members you think Employee needs.

  • No: employee can’t copy in real world.
Employee(const Employee&) = delete;Employee &operator=(const Employee&) = delete;

Exercise 13.20

Explain what happens when we copy, assign, or destroy objects of our TextQuery and QueryResult classes from §12.3(p. 484).

  • The member(smart pointer and container) will be copied.

Exercise 13.21

Do you think the TextQuery and QueryResult classes need to define their own versions of the copy-control members? If so, why? If not, why not? Implement whichever copy-control operations you think these classes require.

  • The synthesized version meet all requirements for this case.

13.2. Copy Control and Resource Management

  • Classes that manage resources which don’t reside in the class must define the copy-control members, they will need destructors to free the resources allocated by the object. Once a class needs a destructor, it also needs a copy constructor and copy-assignment operator.
  • We should first decide what copying an object of our type will mean. In general, we have two choices: define the copy operations to make the class behave like a value or like a pointer.
    1. Classes that behave like values have their own state. When we copy a valuelike object, the copy and the original are independent of each other.
    2. Classes that act like pointers share state. When we copy objects of such classes, the copy and the original use the same underlying data.
  • The library containers and string class have valuelike behavior, the shared_ptr class provides pointerlike behavior. The IO types and unique_ptr don’t allow copying or assignment, so they provide neither valuelike nor pointerlike behavior.
  • To illustrate these two approaches, we first make the HasPtr class act like a value; then reimplement the class making it behave like a pointer.
  • HasPtr class has two members, an int and a pointer to string. Classes copy members of built-in type(other than pointers) directly; such members are values and behave like values. What we do when we copy the pointer member determines whether a class like HasPtr has valuelike or pointerlike behavior.

Exercises Section 13.2

Exercise 13.22

Assume that we want HasPtr to behave like a value. That is, each object should have its own copy of the string to which the objects point. We’ll show the definitions of the copy-control members in the next section. However, you already know everything you need to know to implement these members. Write the HasPtr copy constructor and copy-assignment operator before reading on.

#include <iostream>#include <string>using namespace std;class HasPtr{public:    HasPtr(const string &s = string()) : ps(new string(s)), id(0) {}    HasPtr(const HasPtr &orig) : ps(new string(*(orig.ps))), id(orig.id) {}    HasPtr &operator=(const HasPtr &rhs)    {        ps = new string(*(rhs.ps));        id = rhs.id;    }    ~HasPtr()    {        delete ps;    }    void get_ps() const    {        cout << *ps << '\n';    }    void set_ps(const string &str)    {        delete ps;        ps = new string(str);    }private:    string *ps;    int id;};int main(){    HasPtr obj1("gao"), obj2 = obj1;    obj1.get_ps();    obj2.get_ps();    cout << "After modify obj2:\n";    obj2.set_ps(string("xiang"));    obj1.get_ps();    obj2.get_ps();    return 0;}/*Output:gaogaoAfter modify obj2:gaoxiang*/

13.2.1. Classes That Act Like Values

  • To provide valuelike behavior, each object has to have its own copy of the resource that the class manages, thus HasPtr needs
    • A copy constructor that copies the string, not just the pointer;
    • A destructor to free the string;
    • A copy-assignment operator to free the object’s existing string and copy the string from its right-hand operand.
class HasPtr{public:    HasPtr(const string &s = string()) : ps(new string(s)), id(0) {}    HasPtr(const HasPtr &orig) : ps(new string(*(orig.ps))), id(orig.id) {}    ~HasPtr()    {        delete ps;    }private:    string *ps;    int id;};

Valuelike Copy-Assignment Operator

  • Assignment operators destroys the left-hand operand’s resources and copies data from the right-hand operand. These actions must be done in a sequence that is correct even if an object is assigned to itself. We should write assignment operators so that they will leave the left-hand operand in a sensible state should an exception occur.
  • A good pattern to use when writing an assignment operator is:
    1. Copy the right-hand operand into a local temporary.
    2. Destroy the existing members of the left-hand operand.
    3. Copy the data from the temporary into the members of the left-hand operand.
HasPtr &operator=(const HasPtr &rhs){    string *newp = new string(*(rhs.ps));    delete ps;    ps = newp;    id = rhs.id;    return this;}
// WRONG way to write an assignment operator!HasPtr& operator=(const HasPtr &rhs){    delete ps; // frees the string to which this object points    // if rhs and *this are the same object, we're copying from deleted memory!    ps = new string(*(rhs.ps));    i = rhs.i;    return *this;}

Exercises Section 13.2.1

Exercise 13.23

Compare the copy-control members that you wrote for the solutions to the previous section’s exercises to the code presented here. Be sure you understand the differences, if any, between your code and ours.

  • Yes.

Exercise 13.24

What would happen if the version of HasPtr in this section didn’t define a destructor? What if HasPtr didn’t define the copy constructor?

  • The memory pointed by ps won’t be free.
  • Only copy pointer itself, not string itself.

Exercise 13.25

Assume we want to define a version of StrBlob that acts like a value. Also assume that we want to continue to use a shared_ptr so that our StrBlobPtr class can still use a weak_ptr to the vector. Your revised class will need a copy constructor and copy-assignment operator but will not need a destructor. Explain what the copy constructor and copy-assignment operators must do. Explain why the class does not need a destructor.

  • Copy constructor and copy-assignment operator should dynamically allocate memory for its own data, rather than share the object with the right hand operand.
  • StrBlob is using smart pointers which can be managed with synthesized destructor, If an object of StrBlob is out of scope, the destructor for std::shared_ptr will be called automatically to free the memory dynamically allocated when the use_count goes to 0.

Exercise 13.26

Write your own version of the StrBlob class described in the previous exercise.

#include <iostream>#include <string>#include <vector>#include <memory>using namespace std;class StrBlobPtr;class StrBlob{    friend class StrBlobPtr;public:    StrBlobPtr begin();    StrBlobPtr end();    typedef vector<string>::size_type   size_type;    StrBlob(string id) : id_(id), data(make_shared<vector<string>> ())    {        cout << "Constructor:\tid = " << id_ << '\n';    }    StrBlob(string id, initializer_list<string> il) :        id_(id), data(make_shared<vector<string>>(il))    {        cout << "Constructor:\tid = " << id_ << '\n';    }    StrBlob(const StrBlob &obj)    {        id_ = obj.id_;        data = make_shared<vector<string>>(*obj.data);    }    StrBlob &operator=(const StrBlob &rhs)    {        id_ = rhs.id_;        data = make_shared<vector<string>>(*rhs.data);        return *this;    }    ~StrBlob()    {        cout << "Destructor:\tid = " << id_ << '\n';    }    size_type size() const    {        return data->size();    }    bool empty() const    {        return data->empty();    }    void push_back(const string &t)    {        data->push_back(t);    }    void pop_back()    {        check(0, "pop_back on empty StrBlob");        data->pop_back();    }    string &front()    {        check(0, "front on empty StrBlob");        return data->front();    }    string &back()    {        check(0, "back on empty StrBlob");        return data->back();    }    const string &front() const    {        check(0, "front on empty StrBlob");        return data->front();    }    const string &back() const    {        check(0, "back on empty StrBlob");        return data->back();    }    void Print()    {        int length = data->size();        for(int index = 0; index < length; ++index)        {            cout << (*data)[index] << ' ';        }        cout << '\n';    }private:    string id_;    shared_ptr<vector<string>> data;    // throws msg if data[i] isn't valid    void check(size_type i, const string &msg) const    {        if(i >= data->size())        {            throw out_of_range(msg);        }    }};// StrBlobPtr throws an exception on attempts to access a nonexistent elementclass StrBlobPtr{public:    StrBlobPtr() : curr(0) {}    StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}    StrBlobPtr(const StrBlobPtr &obj) : wptr(obj.wptr), curr(obj.curr) {}    StrBlobPtr &operator=(const StrBlobPtr &rhs)    {        wptr = rhs.wptr;        curr = rhs.curr;        return *this;    }    string &deref() const    {        shared_ptr<vector<string>> p = check(curr, "dereference past end");        return (*p)[curr];    }    // prefix: return a reference to the incremented object    StrBlobPtr &incr()    {        check(curr, "increment past end of StrBlobPtr");        ++curr;        return *this;    }    bool operator!=(const StrBlobPtr& p)    {        return p.curr != curr;    }private:    // check returns a shared_ptr to the vector if the check succeeds    shared_ptr<vector<string>> check(size_t i, const string &msg) const    {        shared_ptr<vector<string>> ret = wptr.lock();        if(!ret)        {            throw runtime_error("unbound StrBlobPtr");        }        if(i >= ret->size())        {            throw out_of_range(msg);        }        return ret;    }    // store a weak_ptr, which means the underlying vector might be destroyed    weak_ptr<vector<string>> wptr;    size_t curr; // current position within the array};StrBlobPtr StrBlob::begin(){    return StrBlobPtr(*this);}StrBlobPtr StrBlob::end(){    StrBlobPtr ret = StrBlobPtr(*this, data->size());    return ret;}int main(){    return 0;}

13.2.2. Defining Classes That Act Like Pointers

  • For HasPtr class to act like a pointer, we need the copy constructor and copy-assignment operator to copy the pointer member, not the string to which that pointer points. Our destructor can free the memory allocated by the constructor that takes a string only when the last HasPtr pointing to that string goes away.
  • The easiest way to make a class act like a pointer is to use shared_ptrs to manage the resources in the class. Copying(or assigning) a shared_ptr copies(assigns) the pointer to which the shared_ptr points. The shared_ptr class itself keeps track of how many users are sharing the pointed-to object. When there are no more users, the shared_ptr class takes care of freeing the resource.
    We can manage a resource directly by using a reference count(§12.1.1).

Reference Counts

  • Reference counting works as follows:
    • In addition to initializing the object, each constructor(except copy constructor) creates a counter that keeps track of how many objects share state with the object we are creating and initialize the counter to 1.
    • The copy constructor copies the data members of its given object and increments the shared counter by 1.
    • The destructor decrements the counter and if the count goes to zero, the destructor deletes that state.
    • The copy-assignment operator increments the right-hand operand’s counter and decrements the counter of the left-hand operand. If the counter for the left-hand operand goes to zero, it must destroy the state of the left-hand operand.
  • When we create an object, we allocate a new counter in dynamic memory. When we copy or assign an object, we copy the pointer to the counter. That way the copy and the original will point to the same counter.

Defining a Reference-Counted Class

class HasPtr{public:    HasPtr(const string &s = string()) :        ps(new string(s)), id(0), use(new size_t(1)) {}    HasPtr(const HasPtr &orig) : ps(orig.ps), id(orig.id), use(orig.use)    {        ++*use;    }    HasPtr &operator=(const HasPtr &rhs);    ~HasPtr();private:    string *ps;    int id;    size_t *use;};

Pointerlike Copy Members “Fiddle” the Reference Count

~HasPtr(){    if(--*use == 0)    {        delete ps;        delete use;    }}
  • The copy assignment operator must handle self-assignment: increment the count in rhs before decrementing the count in the left-hand object. If both objects are the same, the counter will have been incremented before we check to see if ps(and use) should be deleted.
HasPtr &operator=(const HasPtr &rhs){    ++*rhs.use;    if(--*use == 0)    {        delete ps;        delete use;    }    ps = rhs.ps;    id = rhs.id;    use = rhs.use;    return *this;}

Exercises Section 13.2.2

Exercise 13.27

Define your own reference-counted version of HasPtr.

#include <iostream>#include <string>using namespace std;class HasPtr{public:    HasPtr(const string &s1, const string &s2) :        name(s1), ps(new string(s2)), id(0), use(new size_t(1))    {        cout << "Constructor:\tname = " << name << ", *ps = " << *ps             << ", ref_count = " << *use << '\n';    }    HasPtr(const HasPtr &orig) : ps(orig.ps), id(orig.id), use(orig.use)    {        ++*use;        cout << "Copy-Construct:\tname = " << name << ", *ps = " << *ps             << ", ref_count = " << *use << '\n';    }    HasPtr &operator=(const HasPtr &rhs)    {        cout << "Copy-operator:\t";        cout << "left-hand: name = " << name << ", *ps = " << *ps             << ", ref_count = " << *use << '\n';        cout << "\t\tright-hand: name = " << rhs.name << ", *ps = " << *rhs.ps             << ", ref_count = " << *rhs.use << '\n';        ++*rhs.use;        if(--*use == 0)        {            cout << "\t\tRunning left-hand obj's Destructor.\n";            delete ps;            delete use;        }        ps = rhs.ps;        id = rhs.id;        use = rhs.use;        cout << "Copy-op finish:\tname = " << name << ", *ps = " << *ps             << ", ref_count = " << *use << '\n';        return *this;    }    ~HasPtr()    {        cout << "Destructor:\tBefore --*use: name = " << name << ", *ps = " << *ps             << ", ref_count = " << *use << '\n';        if(--*use == 0)        {            delete ps;            delete use;            cout << "\t\t--*use = 0: destroy object.\n";        }        else        {            cout << "\t\t--*use = " << *use << ", not destroy object.\n";        }    }    void get_ps() const    {        cout << *ps << '\n';    }    void set_ps(const string &str)    {        delete ps;        ps = new string(str);    }private:    string name = "default";    string *ps;    int id;    size_t *use;};int main(){    HasPtr obj1("obj1", "gao");    HasPtr obj2(obj1);    HasPtr obj3("obj3", "xiang");    obj3 = obj2;    obj2.set_ps("gaoxiangnumber1");    return 0;}/*Output:Constructor:    name = obj1, *ps = gao, ref_count = 1Copy-Construct: name = default, *ps = gao, ref_count = 2Constructor:    name = obj3, *ps = xiang, ref_count = 1Copy-operator:  left-hand: name = obj3, *ps = xiang, ref_count = 1        right-hand: name = default, *ps = gao, ref_count = 2        Running left-hand obj's Destructor.Copy-op finish: name = obj3, *ps = gao, ref_count = 3Destructor: Before --*use: name = obj3, *ps = gaoxiangnumber1, ref_count = 3        --*use = 2, not destroy object.Destructor: Before --*use: name = default, *ps = gaoxiangnumber1, ref_count = 2        --*use = 1, not destroy object.Destructor: Before --*use: name = obj1, *ps = gaoxiangnumber1, ref_count = 1        --*use = 0: destroy object.*/

Exercise 13.28

Given the following classes, implement a default constructor and the necessary copy-control members.

// (a)class TreeNode{private:    std::string value;    int count;    TreeNode *left;    TreeNode *right;};// (b)class BinStrTree{private:    TreeNode *root;};
#include <string>using namespace std;class TreeNode{public:    TreeNode() : value(string()), count(new int(1)), left(nullptr), right(nullptr) {}    TreeNode(const TreeNode& rhs) :        value(rhs.value), count(rhs.count), left(rhs.left), right(rhs.right)    {        ++*count;    }    TreeNode& operator=(const TreeNode& rhs);    ~TreeNode()    {        if (--*count == 0)        {            if (left)            {                delete left;                left = nullptr;            }            if (right)            {                delete right;                right = nullptr;            }            delete count;            count = nullptr;        }    }private:    string value;    int* count;    TreeNode* left;    TreeNode* right;};class BinStrTree{public:    BinStrTree() : root(new TreeNode()) {}    BinStrTree(const BinStrTree& bst) : root(new TreeNode(*bst.root)) {}    BinStrTree& operator=(const BinStrTree& bst);    ~BinStrTree()    {        delete root;    }private:    TreeNode* root;};TreeNode& TreeNode::operator=(const TreeNode& rhs){    ++*rhs.count;    if (--*count == 0)    {        if (left)        {            delete left;            left = nullptr;        }        if (right)        {            delete right;            right = nullptr;        }        delete count;        count = nullptr;    }    value = rhs.value;    left = rhs.left;    right = rhs.right;    count = rhs.count;    return *this;}BinStrTree& BinStrTree::operator=(const BinStrTree& bst){    TreeNode* new_root = new TreeNode(*bst.root);    delete root;    root = new_root;    return *this;}

13.3. Swap

  • Defining swap is important for classes that use with algorithms that reorder elements (§10.2.3). Such algorithms call swap whenever they need to exchange two elements. If a class defines its own swap, then the algorithm uses that class-specific version; otherwise, it uses the swap function defined by the library.
HasPtr temp = v1;   // make a temporary copy of the value of v1v1 = v2;                // assign the value of v2 to v1v2 = temp;          // assign the saved value of v1 to v2
  • This code copies the string in v1 twice: once when the HasPtr copy constructor copies v1 into temp and again when the assignment operator assigns temp to v2. Copying a valuelike HasPtr allocates a new string and copies the string to which the HasPtr points.
  • None of this memory allocation is necessary. Rather than allocating new copies of the string, we’d like swap to swap the pointers.
string *temp = v1.ps;   // make a temporary copy of the pointer in v1.psv1.ps = v2.ps;      // assign the pointer in v2.ps to v1.psv2.ps = temp;       // assign the saved pointer in v1.ps to v2.ps

Writing Our Own swap Function

class HasPtr{    friend void swap(HasPtr&, HasPtr&);    // other members as in § 13.2.1};inline void swap(HasPtr &lhs, HasPtr &rhs){    using std::swap;    swap(lhs.ps, rhs.ps);   // swap the pointers, not the string data    swap(lhs.i, rhs.i);     // swap the int members}

swap Functions Should Call swap, Not std::swap

  • If there is a type-specific version of swap, call swap() matches this version(because type-specific version is a better match than std::swap(explain in §16.3)), otherwise call swap() uses std::swap(if declared before).

Using swap in Assignment Operators

  • Copy assignment operators can use “copy and swap” technique that swaps the left-hand operand with a copy of the right-hand operand.
// rhs is passed by value, means HasPtr copy constructor// copies the string in the right-hand operand into rhsHasPtr& HasPtr::operator=(HasPtr rhs){    // swap the contents of the left-hand operand with the local variable rhs    swap(*this, rhs);   // rhs now points to the memory this object had used    return *this;       // rhs is destroyed, which deletes the pointer in rhs}
    1. We pass the right-hand operand by value, so, rhs is a copy of the right-hand operand. Copying a HasPtr allocates a new copy of that object’s string.
    2. swap(*this, rhs); swaps the data members of rhs with those in *this. After swap, the pointer member in *this points to the newly allocated string that is a copy of the right-hand operand.
    3. When the assignment operator finishes, rhs is destroyed and the HasPtr destructor is run. That destructor deletes the memory to which rhs now points(i.e., the memory to which the left-hand operand had pointed).
  • This technique handles self assignment and is exception safe.
    1. Self assignment: copy the right-hand operand before changing the left-hand operand.
    2. Exception safe: the same way as the original definition. The only code that may throw is new() inside the copy constructor. If an exception occurs, it will happen before we have changed the left-hand operand.

Exercises Section 13.3

Exercise 13.29

Explain why the calls to swap inside swap(HasPtr&, HasPtr&) don’t cause a recursion loop.

  • Because we declare using std::swap; before and for built-in types, std::swap is a better match.

Exercise 13.30

Write and test a swap function for your valuelike version of HasPtr. Give your swap a print statement that notes when it is executed.

#include <iostream>#include <string>using namespace std;class HasPtr{    friend void swap(HasPtr &, HasPtr&);public:    HasPtr(const string &s = string()) : ps(new string(s)), id(0) {}    HasPtr(const HasPtr &orig) : ps(new string(*(orig.ps))), id(orig.id) {}    HasPtr &operator=(const HasPtr &rhs)    {        ps = new string(*(rhs.ps));        id = rhs.id;    }    ~HasPtr()    {        delete ps;    }    const string &get_ps() const    {        return *ps;    }    void set_ps(const string &str)    {        delete ps;        ps = new string(str);    }private:    string *ps;    int id;};inline void swap(HasPtr &lhs, HasPtr &rhs){    cout << "\nswap value-like HasPtr\n";    swap(lhs.ps, rhs.ps);    swap(lhs.id, rhs.id);}int main(){    HasPtr obj1("gao"), obj2 = obj1;    cout << "obj1.get_ps() = " << obj1.get_ps() << '\n';    cout << "obj2.get_ps() = " << obj2.get_ps() << '\n';    cout << "\nAfter modify obj2:\n";    obj2.set_ps(string("xiang"));    cout << "obj1.get_ps() = " << obj1.get_ps() << '\n';    cout << "obj2.get_ps() = " << obj2.get_ps() << '\n';    swap(obj1, obj2);    cout << "\nAfter swap obj1 and obj2:\n";    cout << "obj1.get_ps() = " << obj1.get_ps() << '\n';    cout << "obj2.get_ps() = " << obj2.get_ps() << '\n';    return 0;}/*Output:obj1.get_ps() = gaoobj2.get_ps() = gaoAfter modify obj2:obj1.get_ps() = gaoobj2.get_ps() = xiangswap value-like HasPtrAfter swap obj1 and obj2:obj1.get_ps() = xiangobj2.get_ps() = gao*/

Exercise 13.31

Give your class a < operator and define a vector of HasPtr. Give that vector some elements and then sort the vector. Note when swap is called.

#include <iostream>#include <string>#include <vector>#include <algorithm>using namespace std;class HasPtr{    friend bool operator<(const HasPtr &, const HasPtr&);public:    HasPtr(const string &s = string()) : ps(new string(s)), id(0) {}    HasPtr(const HasPtr &orig) : ps(new string(*(orig.ps))), id(orig.id) {}    //HasPtr &operator=(HasPtr rhs) = delete;    HasPtr &operator=(HasPtr rhs)    {        cout << "operator=: left-obj = " << *ps <<             ", right-obj = " << *rhs.ps << ". -> ";        this->swap(rhs);        return *this;    }    ~HasPtr()    {        delete ps;    }    const string &get_ps() const    {        return *ps;    }    void set_ps(const string &str)    {        delete ps;        ps = new string(str);    }    void swap(HasPtr &rhs)    {        cout << *ps << ".swap(" << *rhs.ps << ")\n";        std::swap(ps, rhs.ps);        std::swap(id, rhs.id);    }private:    string *ps;    int id;};bool operator<(const HasPtr &lhs, const HasPtr &rhs){    return *lhs.ps < *rhs.ps;}int main(){    HasPtr obj1("d"), obj2("c"), obj3("b"), obj4("a");    vector<HasPtr> vec {obj1, obj2, obj3, obj4};    cout << "Before sort:\t";    for(int index = 0; index < 4; ++index)    {        cout << vec[index].get_ps() << ' ';    }    cout << "\n\n";    sort(vec.begin(), vec.end());    cout << "\nAfter sort:\t";    for(int index = 0; index < 4; ++index)    {        cout << vec[index].get_ps() << ' ';    }    return 0;}/*Output1: when define copy-assignment operatorBefore sort:    d c b aoperator=: left-obj = c, right-obj = d. -> c.swap(d)operator=: left-obj = d, right-obj = c. -> d.swap(c)operator=: left-obj = b, right-obj = d. -> b.swap(d)operator=: left-obj = d, right-obj = c. -> d.swap(c)operator=: left-obj = c, right-obj = b. -> c.swap(b)operator=: left-obj = a, right-obj = d. -> a.swap(d)operator=: left-obj = d, right-obj = c. -> d.swap(c)operator=: left-obj = c, right-obj = b. -> c.swap(b)operator=: left-obj = b, right-obj = a. -> b.swap(a)After sort: a b c d*//*Output2: when not define copy-assignment operatorBefore sort:    d c b a*** Error in `cpp': free(): invalid pointer: 0x0000000001d57148 ***After sort: !�q� !�q� !�q� d Aborted*//*Output3: when "HasPtr &operator=(HasPtr rhs) = delete;"error: use of deleted function ‘HasPtr& HasPtr::operator=(HasPtr)’*/

Exercise 13.32

Would the pointerlike version of HasPtr benefit from defining a swap function? If so, what is the benefit? If not, why not?

  • Avoiding memory allocation is the reason why it improve performance. As for the pointerlike version, no dynamic memory allocation anyway. Thus, a specific version for it will not improve the performance.

13.4. A Copy-Control Example

  • Classes Message and Folder represent email messages and directories in which a message might appear. Each Message can appear in multiple Folders, but there will be only one copy of the contents of any Message. Each Message will store a set of pointers to the Folders in which it appears, and each Folder will contain a set of pointers to its Messages. Figure 13.1.

  • Message class will provide save and remove operations to add or remove a Message from a specified Folder. To create a new Message, we specify the contents of the message but no Folder. To put a Message in a particular Folder, we must call save.
  • When we copy a Message, the copy and the original will be distinct Messages, but both Messages should appear in the same set of Folders. Thus, copying a Message will copy the contents and the set of Folder pointers. It must also add a pointer to the newly created Message to each of those Folders.
  • When we destroy a Message, that Message no longer exists. Therefore, destroying a Message must remove pointers to that Message from the Folders that had contained that Message.
  • When we assign one Message to another, we’ll replace the contents of the left-hand Message with those in the right-hand side. We must also update the set of Folders, removing the left-hand Message from its previous Folders and adding that Message to the Folders in which the right-hand Message appears.
  • Both the destructor and the copy-assignment operator have to remove this Message from the Folders that point to it, both the copy constructor and the copy-assignment operator add a Message to a given list of Folders. We’ll define a pair of private utility functions to do these tasks.
    The copy-assignment operator often does the same work as is needed in the copy constructor and destructor. In such cases, the common work should be put in private utility functions.

The Message Class

class Message{    friend class Folder;public:    // folders is implicitly initialized to the empty set    explicit Message(const string &str = ""): contents(str) {}    // copy control to manage pointers to this Message    Message(const Message&);                // copy constructor    Message& operator=(const Message&); // copy assignment    ~Message(); // destructor    // add/remove this Message from the specified Folder's set of messages    void save(Folder&);    void remove(Folder&);private:    string contents;            // actual message text    set<Folder*> folders;       // Folders that have this Message    // utility functions used by copy constructor, assignment, and destructor    // add this Message to the Folders that point to the parameter    void add_to_Folders(const Message&);    // remove this Message from every Folder in folders    void remove_from_Folders();};

The save and remove Members

// add/remove this Message from the specified Folder's set of messagesvoid save(Folder &f){    folders.insert(&f); // add the given Folder to our list of Folders    f.addMsg(this);     // add this Message to f's set of Messages}void remove(Folder &f){    folders.erase(&f);  // take the given Folder out of our list of Folders    f.remMsg(this);     // remove this Message to f's set of Messages}

Copy Control for the Message Class

  • When we copy a Message, the copy should appear in the same Folders as the original Message. So, we must traverse the set of Folder pointers adding a pointer to the new Message to each Folder that points to the original Message.
void add_to_Folders(const Message &m){    // for each Folder that holds m    for(set<Folder*>::iterator f = m.folders.begin(); f != m.folders.end(); ++f)    {        f->addMsg(this); // add a pointer to this Message to that Folder    }}Message(const Message &m): contents(m.contents), folders(m.folders){    add_to_Folders(m); // add this Message to the Folders that point to m}

The Message Destructor

  • When a Message is destroyed, we must remove this Message from the Folders that point to it. This work is shared with the copy-assignment operator, so we define a common function to do it.
// remove this Message from every Folder in foldersvoid remove_from_Folders(){    // for each Folder that holds m    for(set<Folder*>::iterator f = m.folders.begin(); f != m.folders.end(); ++f)    {        f->remMsg(this); // remove this Message from that Folder    }}~Message() // destructor{    remove_from_Folders();}
  • The call to remove_from_Folders ensures that no Folder has a pointer to the Message we are destroying. The compiler automatically invokes the string destructor to free contents and the set destructor to clean up the memory used by those members.

Message Copy-Assignment Operator

  • Copy-assignment operator must do the work of the copy constructor and the destructor and it must be right even if the left- and right-hand operands happen to be the same object.
  • We protect against self-assignment by removing pointers to this Message from the folders of the left-hand operand before inserting pointers in the folders in the right-hand operand.
Message& operator=(const Message &rhs) // copy assignment{    // handle self-assignment by removing pointers before inserting them    remove_from_Folders();  // update existing Folders    contents = rhs.contents;    // copy message contents from rhs    folders = rhs.folders;      // copy Folder pointers from rhs    add_to_Folders(rhs);        // add this Message to those Folders    return *this;}

A swap Function for Message

  • swap must manage the Folder pointers that point to the swapped Messages. After a call swap(m1, m2);, the Folders that had pointed to m1 must now point to m2, and vice versa.
  • We manage the Folder pointers by making two passes through each of the folders members: the first pass removes the Messages from their respective Folders; we next call swap to swap the data members; then the second pass adds pointers to the swapped Messages.
void swap(Message &lhs, Message &rhs){    // remove pointers to each Message from their(original) respective Folders    for(set<Folder*>::iterator f = lhs.folders.begin(); f != lhs.folders.end(); ++f)    {        f->remMsg(&lhs); // remove this Message from that Folder    }    for(set<Folder*>::iterator f = rhs.folders.begin(); f != rhs.folders.end(); ++f)    {        f->remMsg(&rhs); // remove this Message from that Folder    }    // swap the contents and Folder pointer sets    swap(lhs.contents, rhs.contents); // swap(string&, string&)    swap(lhs.folders, rhs.folders); // swap(set&, set&)    // add pointers to each Message to their(new) respective Folders    for(set<Folder*>::iterator f = lhs.folders.begin(); f != lhs.folders.end(); ++f)    {        f->addMsg(&lhs); // remove this Message from that Folder    }    for(set<Folder*>::iterator f = rhs.folders.begin(); f != rhs.folders.end(); ++f)    {        f->addMsg(&rhs); // remove this Message from that Folder    }}

Exercises Section 13.4

Exercise 13.33

Why is the parameter to the save and remove members of Message a Folder&? Why didn’t we define that parameter as Folder? Or const Folder&?

  • If Folder: we will change the temporary copied object, not the original folder object.
  • If const Folder&: we can’t change the folder object since it is const.

Exercise 13.34

Write the Message class as described in this section.

  • See 13.36.

Exercise 13.35

What would happen if Message used the synthesized versions of the copy-control members?

  • Some existing Folders will out of sync with the Message after assignment.

Exercise 13.36

Design and implement the corresponding Folder class. That class should hold a set that points to the Messages in that Folder.

// .h#include <string>#include <set>using namespace std;class Folder;class Message{    friend void swap(Message&, Message&);    friend class Folder;public:    // folders is implicitly initialized to the empty set    explicit Message(const string &str = ""): contents(str) {}    // copy control to manage pointers to this Message    Message(const Message &m);    Message& operator=(const Message &rhs);    ~Message();    // add/remove this Message from the specified Folder's set of messages    void save(Folder &f);    void remove(Folder &f);private:    string contents; // actual message text    set<Folder*> folders; // Folders that have this Message    // utility functions used by copy constructor, assignment, and destructor    // add this Message to the Folders that point to the parameter    void add_to_Folders(const Message &m);    // remove this Message from every Folder in folders    void remove_from_Folders();    void addFldr(Folder *f)    {        folders.insert(f);    }    void remFldr(Folder *f)    {        folders.erase(f);    }};void swap(Message &lhs, Message &rhs);class Folder{    friend void swap(Folder&, Folder&);    friend class Message;public:    Folder() = default;    Folder(const Folder &f);    Folder& operator=(const Folder &);    ~Folder();private:    std::set<Message*> msgs;    void add_to_Message(const Folder &f);    void remove_from_Message();    void addMsg(Message *m)    {        msgs.insert(m);    }    void remMsg(Message *m)    {        msgs.erase(m);    }};void swap(Folder &lhs, Folder &rhs);
// .cpp#include <iostream>#include "msgfldr.h"Message::Message(const Message &m): contents(m.contents), folders(m.folders){    add_to_Folders(m); // add this Message to the Folders that point to m}Message& Message::operator=(const Message &rhs) // copy assignment{    // handle self-assignment by removing pointers before inserting them    remove_from_Folders(); // update existing Folders    contents = rhs.contents; // copy message contents from rhs    folders = rhs.folders; // copy Folder pointers from rhs    add_to_Folders(rhs); // add this Message to those Folders    return *this;}Message::~Message() // destructor{    remove_from_Folders();}// add/remove this Message from the specified Folder's set of messagesvoid Message::save(Folder &f){    folders.insert(&f); // add the given Folder to our list of Folders    f.addMsg(this); // add this Message to f's set of Messages}void Message::remove(Folder &f){    folders.erase(&f); // take the given Folder out of our list of Folders    f.remMsg(this); // remove this Message to f's set of Messages}void Message::add_to_Folders(const Message &m){    // for each Folder that holds m    for(set<Folder*>::iterator f = m.folders.begin(); f != m.folders.end(); ++f)    {        (*f)->addMsg(this); // add a pointer to this Message to that Folder    }}// remove this Message from every Folder in foldersvoid Message::remove_from_Folders(){    // for each Folder that holds m    for(set<Folder*>::iterator f = folders.begin(); f != folders.end(); ++f)    {        (*f)->remMsg(this); // remove this Message from that Folder    }}void swap(Message &lhs, Message &rhs){    // remove pointers to each Message from their(original) respective Folders    lhs.remove_from_Folders();    rhs.remove_from_Folders();    // swap the contents and Folder pointer sets    swap(lhs.contents, rhs.contents); // swap(string&, string&)    swap(lhs.folders, rhs.folders); // swap(set&, set&)    // add pointers to each Message to their(new) respective Folders    lhs.add_to_Folders(lhs);    rhs.add_to_Folders(rhs);}Folder::Folder(const Folder &f): msgs(f.msgs){    add_to_Message(f);}Folder& Folder::operator=(const Folder &rhs){    remove_from_Message();    msgs = rhs.msgs;    add_to_Message(rhs);    return *this;}Folder::~Folder(){    remove_from_Message();}void Folder::add_to_Message(const Folder &f){    for(set<Message*>::iterator m = f.msgs.begin(); m != f.msgs.end(); ++m)    {        (*m)->addFldr(this);    }}void Folder::remove_from_Message(){    for(set<Message*>::iterator m = msgs.begin(); m != msgs.end(); ++m)    {        (*m)->remFldr(this);    }}void swap(Folder &lhs, Folder &rhs){    lhs.remove_from_Message();    rhs.remove_from_Message();    swap(lhs.msgs, rhs.msgs);    lhs.add_to_Message(lhs);    rhs.add_to_Message(rhs);}int main(){    Message msg("gaoxiangnumber1");    Folder fldr;    return 0;}

Exercise 13.37

Add members to the Message class to insert or remove a given Folder* into folders. These members are analogous to Folder’s addMsg and remMsg operations.

  • Same as before.

Exercise 13.38

We did not use copy and swap to define the Message assignment operator. Why do you suppose this is so?

  • In this case, swap function is special. It will be clear two Message’s folders , then swap members, and added themselves to each folders. But, Message assignment operator just clear itself, and copy the members, and added itself to each folders. The rhs don’t need to clear and add to folders. So, if using copy and swap to define, it will be very inefficiency.

13.5. Classes That Manage Dynamic Memory

  • We implement the library vector class(not template), only hold strings(StrVec).

StrVec Class Design

  • The vector class stores its elements in contiguous storage. To obtain performance, vector preallocates enough storage to hold more elements than needed(§9.4). Each vector member that adds elements checks whether there is space available for another element.
    1. If so, the member constructs an object in the next available spot.
    2. If there isn’t space left, then the vector is reallocated: The vector obtains new space, moves the existing elements into that space, frees the old space, and adds the new element.

  • We’ll use an allocator to obtain raw memory(§12.2.2, Table 12-7). Because the memory an allocator allocates is unconstructed, we’ll use the allocator’s construct member to create objects in that space when we need to add an element.
    When we remove an element, we’ll use the destroy member to destroy the element.

  • Figure 13.2. Each StrVec will have three pointers into the space it uses for its elements:
    1. elements points to the first element in the allocated memory.
    2. first_free points just after the last actual element.
    3. cap points just past the end of the allocated memory.
  • StrVec will have a member alloc that is an allocator. The alloc member will allocate the memory used by a StrVec. Our class will have four utility functions:
    1. alloc_n_copy allocate space and copy a given range of elements.
    2. free destroy the constructed elements and deallocate the space.
    3. chk_n_alloc ensure that there is room to add at least one more element to StrVec. If there isn’t room for another element, it will call reallocate to get more space.
    4. reallocate will reallocate the StrVec when it runs out of space.

StrVec Class Definition

class StrVec{public:    // the allocator member is default initialized    StrVec(): elements(nullptr), first_free(nullptr), cap(nullptr) {}    StrVec(const StrVec&);              // copy constructor    StrVec &operator=(const StrVec&);   // copy assignment    ~StrVec();                         // destructor    void push_back(const string&);      // copy the element    size_t size() const    {        return first_free - elements;    }    size_t capacity() const    {        return cap - elements;    }    string *begin() const    {        return elements;    }    string *end() const    {        return first_free;    }private:    allocator<string> alloc;    // allocates the elements    void chk_n_alloc()      // used by the functions that add elements to the StrVec    {        if(size() == capacity())        {            reallocate();        }    }    // utilities used by the copy constructor, assignment operator, and destructor    pair<string*, string*> alloc_n_copy(const string*, const string*);    void free(); // destroy the elements and free the space    void reallocate();      // get more space and copy the existing elements    string *elements;       // pointer to the first element in the array    string *first_free;     // pointer to the first free element in the array    string *cap;            // pointer to one past the end of the array};

Using construct

  • The push_back function calls chk_n_alloc to ensure that there is room for an element. If necessary, chk_n_alloc will call reallocate. When chk_n_alloc returns, push_back knows that there is room for the new element. It asks its allocator member to construct a new last element.
void StrVec::push_back(const string &s){    chk_n_alloc(); // ensure that there is room for another element    // construct a copy of s in the element to which first_free points    alloc.construct(first_free++, s);}
  • The memory allocated by allocator is unconstructed(§12.2.2). To use this raw memory we must call construct, which will construct an object in that memory.
    The first argument to construct must be a pointer to unconstructed space allocated by a call to allocate. The remaining arguments determine which constructor to use to construct the object that will go in that space. In this case, this call uses the string copy constructor.

The alloc_n_copy Member

  • StrVec class will have valuelike behavior(§13.2.1): when we copy or assign a StrVec, we have to allocate independent memory and copy the elements from the original to the new StrVec.
  • The alloc_n_copy member will allocate enough storage to hold its given range of elements, and copy those elements into the newly allocated space. This function returns a pair(§11.2.3) of pointers, pointing to the beginning of the new space and just past the last element it copied.
pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e){    // allocate space to hold as many elements as are in the range    void *data = alloc.allocate(e - b);    // initialize and return a pair constructed from data and    // the value returned by uninitialized_copy    return {data, uninitialized_copy(b, e, data)};}
  • alloc_n_copy calculates how much space to allocate by subtracting the pointer to the first element from the pointer one past the last. Having allocated memory, the function next has to construct copies of the given elements in that space.
  • It does the copy in the return statement, which list initializes the return value(§6.3.2). The first member of the returned pair points to the start of the allocated memory; the second is the value returned from uninitialized_copy(§12.2.2). That value will be pointer positioned one element past the last constructed element.

The free Member

  • The free member has two responsibilities: destroy the elements and then deallocate the space that this StrVec allocated.
void StrVec::free(){    // may not pass deallocate a 0 pointer; if elements is 0, there's no work to do    if(elements)    {        // destroy the old elements in reverse order        for(string *p = first_free; p != elements; )        {            alloc.destroy(--p);        }        alloc.deallocate(elements, cap - elements);    }}
  • Once the elements have been destroyed, we free the space that this StrVec allocated by calling deallocate. The pointer we pass to deallocate must be one that was previously generated by a call to allocate. Therefore, we first check that elements isn’t null before calling deallocate.

Copy-Control Members

StrVec::StrVec(const StrVec &s){    // call alloc_n_copy to allocate exactly as many elements as in s    pair<string*, string*> newdata = alloc_n_copy(s.begin(), s.end());    elements = newdata.first;    first_free = cap = newdata.second;}
  • The copy constructor calls alloc_n_copy and assigns the results to the data members. The first pointer points to the first constructed element and the second points just past the last constructed element. Because alloc_n_copy allocates space for exactly as many elements as it is given, cap also points just past the last constructed element.
  • The copy-assignment operator calls alloc_n_copy before freeing its existing elements. By doing so it protects against self-assignment.
StrVec::~StrVec(){    free();}StrVec &StrVec::operator=(const StrVec &rhs){    // call alloc_n_copy to allocate exactly as many elements as in rhs    pair<string*, string*> data = alloc_n_copy(rhs.begin(), rhs.end());    free();    elements = data.first;    first_free = cap = data.second;    return *this;}

Moving, Not Copying, Elements during Reallocation

  • The reallocate function will
    1. Allocate memory for a new, larger array of strings.
    2. Construct the first part of that space to hold the existing elements.
    3. Destroy the elements in the existing memory and deallocate that memory.
  • Copying a string must allocate memory for those characters, and destroying a string must free the memory used by that string. When reallocate copies the strings in a StrVec, there will be only one user of these strings after the copy. As soon as we copy the elements from the old space to the new, we will immediately destroy the original strings. So, copying the data in these strings is unnecessary.

Move Constructors and std::move

  • We can avoid copying the strings by using two facilities introduced by C++11.
    1. Some library classes(including string) define move constructors that operate by moving resources from the given object to the object being constructed. The library guarantees that the moved-from string remains in a valid, destructible state. Each string has a pointer to an array of char, the move constructor copies the pointer rather than allocating space for and copying the characters themselves.
    2. A library function move that is defined in .
      -1- When reallocate constructs the strings in the new memory, it must call move to signal that it wants to use the string move constructor(explain in §13.6.1). If it omits the call to move, the string the copy constructor will be used.
      -2- We usually don’t provide a using declaration for move(explain in §18.2.3). When we use move, we call std::move, not move.

The reallocate Member

void StrVec::reallocate(){    // we'll allocate space for twice as many elements as the current size    size_t newcapacity = size() ? 2 * size() : 1;    // allocate new memory    void *newdata = alloc.allocate(newcapacity);    // move the data from the old memory to the new    void *dest = newdata; // points to the next free position in the new array    string *elem = elements; // points to the next element in the old array    for(size_t i = 0; i != size(); ++i)    {        alloc.construct(dest++, std::move(*elem++));    }    free(); // free the old space once we've moved the elements    // update our data structure to point to the new elements    elements = newdata;    first_free = dest;    cap = elements + newcapacity;}
  • First calling allocate to allocate new space: double the capacity of the StrVec each time we reallocate; if it is empty, we allocate room for one element.
  • The for loop iterates through the existing elements and constructs a corresponding element in the new space. We use dest to point to the memory in which to construct the new string, and use elem to point to an element in the original array.
  • The second argument to construct std::move(*elem++) is the value returned by move. Calling move returns a result that causes construct to use the string move constructor. Each string we construct will take over ownership of the memory from the string to which elem points.
  • After moving the elements, we call free to destroy the old elements and free the memory that this StrVec was using before the call to reallocate. The strings themselves no longer manage the memory to which they had pointed; responsibility for their data has been moved to the elements in the new StrVec memory. Though not know what value the strings in the old StrVec memory have, we are guaranteed that it is safe to run the string destructor on these objects.
  • What remains is to update the pointers to address the newly allocated and initialized array. The first_free and cap pointers are set to denote one past the last constructed element and one past the end of the allocated space, respectively.

Exercises Section 13.5

Exercise 13.39

Write your own version of StrVec, including versions of reserve, capacity(§9.4, p. 356), and resize(§9.3.5, p. 352).

#include <iostream>#include <string>#include <memory>using namespace std;class StrVec{public:    // the allocator member is default initialized    StrVec(): elements(nullptr), first_free(nullptr), cap(nullptr) {}    StrVec(const StrVec&); // copy constructor    StrVec &operator=(const StrVec&); // copy assignment    ~StrVec(); // destructor    size_t size() const    {        return first_free - elements;    }    size_t capacity() const    {        return cap - elements;    }    string *begin() const    {        return elements;    }    string *end() const    {        return first_free;    }    void push_back(const string&); // copy the element    void reserve(size_t);    void resize(size_t);    void resize(size_t, const string&);private:    allocator<string> alloc; // allocates the elements    void chk_n_alloc() // used by the functions that add elements to the StrVec    {        if(size() == capacity())        {            reallocate();        }    }    // utilities used by the copy constructor, assignment operator, and destructor    pair<string*, string*> alloc_n_copy(const string*, const string*);    void alloc_n_move(size_t new_cap);    void free(); // destroy the elements and free the space    void reallocate(); // get more space and copy the existing elements    string *elements; // pointer to the first element in the array    string *first_free; // pointer to the first free element in the array    string *cap; // pointer to one past the end of the array};pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e){    // allocate space to hold as many elements as are in the range    string *data = alloc.allocate(e - b);    // initialize and return a pair constructed from data and    // the value returned by uninitialized_copy    return {data, uninitialized_copy(b, e, data)};}void StrVec::alloc_n_move(size_t newcapacity){    // allocate new memory    string *newdata = alloc.allocate(newcapacity);    // move the data from the old memory to the new    string *dest = newdata; // points to the next free position in the new array    string *elem = elements; // points to the next element in the old array    for(size_t i = 0; i != size(); ++i)    {        alloc.construct(dest++, std::move(*elem++));    }    free(); // free the old space once we've moved the elements    // update our data structure to point to the new elements    elements = newdata;    first_free = dest;    cap = elements + newcapacity;}void StrVec::free(){    // may not pass deallocate a 0 pointer; if elements is 0, there's no work to do    if(elements)    {        // destroy the old elements in reverse order        for(string *p = first_free; p != elements; )        {            alloc.destroy(--p);        }        alloc.deallocate(elements, cap - elements);    }}void StrVec::reallocate(){    // we'll allocate space for twice as many elements as the current size    size_t newcapacity = size() ? 2 * size() : 1;    alloc_n_move(newcapacity);}StrVec::StrVec(const StrVec &s){    // call alloc_n_copy to allocate exactly as many elements as in s    pair<string*, string*> newdata = alloc_n_copy(s.begin(), s.end());    elements = newdata.first;    first_free = cap = newdata.second;}StrVec &StrVec::operator=(const StrVec &rhs){    // call alloc_n_copy to allocate exactly as many elements as in rhs    pair<string*, string*> data = alloc_n_copy(rhs.begin(), rhs.end());    free();    elements = data.first;    first_free = cap = data.second;    return *this;}StrVec::~StrVec(){    free();}void StrVec::push_back(const string &s){    chk_n_alloc(); // ensure that there is room for another element    // construct a copy of s in the element to which first_free points    alloc.construct(first_free++, s);}void StrVec::reserve(size_t newcapacity){    if (newcapacity <= capacity())    {        return;    }    alloc_n_move(newcapacity);}void StrVec::resize(size_t count, const std::string &s){    if (count > size())    {        if (count > capacity())        {            reserve(count * 2);        }        for (size_t i = size(); i != count; ++i)        {            alloc.construct(first_free++, s);        }    }    else if (count < size())    {        while (first_free != elements + count)        {            alloc.destroy(--first_free);        }    }}void StrVec::resize(size_t count){    resize(count, std::string());}int main(){    return 0;}

Exercise 13.40

Add a constructor that takes an initializer_list to your StrVec class.

// Only show the changes: define a new private function// void range_initialize(const string*, const string*);class StrVec{public:    StrVec(initializer_list<string>);    StrVec(const StrVec &s);private:    void range_initialize(const string*, const string*);};pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e){    // allocate space to hold as many elements as are in the range    string *data = alloc.allocate(e - b);    // initialize and return a pair constructed from data and    // the value returned by uninitialized_copy    return {data, uninitialized_copy(b, e, data)};}void StrVec::range_initialize(const string *b, const string *e){    // call alloc_n_copy to allocate exactly as many elements as in s    pair<string*, string*> newdata = alloc_n_copy(b, e);    elements = newdata.first;    first_free = cap = newdata.second;}StrVec::StrVec(initializer_list<string> il){    range_initialize(il.begin(), il.end());}StrVec::StrVec(const StrVec &s){    range_initialize(s.begin(), s.end());}

Exercise 13.41

Why did we use postfix increment in the call to construct inside push_back? What would happen if it used the prefix increment?

  • There will be empty slot.

Exercise 13.42

Test your StrVec class by using it in place of the vector in your TextQuery and QueryResult classes(§12.3, p. 484).

  • Not do.

Exercise 13.43

Rewrite the free member to use for_each and a lambda(§10.3.2, p. 388) in place of the for loop to destroy the elements. Which implementation do you prefer, and why?

  • for_each(elements, first_free, [this](std::string &rhs){ alloc.destroy(&rhs); });
    The new version is better. Compared to the old one, it doesn’t need to worry about the order and decrement. So more straightforward and handy. The only thing to do for using this approach is to add “&” to build the pointers to string pointers.

Exercise 13.44

Write a class named String that is a simplified version of the library string class. Your class should have at least a default constructor and a constructor that takes a pointer to a C-style string. Use an allocator to allocate memory that your String class uses.

  • See 13.47.
    A trivial String class that designed for write-on-paper in an interview

13.6. Moving Objects

  • Conditions that move rather than copy:
    1. When an object is immediately destroyed after it is copied, moving the object can provide better performance than copying.
    2. For classes having a resource(such as a pointer or an IO buffer) that may not be shared, objects can’t be copied but can be moved.
  • Under C++11, we can use containers on types that can’t be copied so long as they can be moved.
  • The library containers, string, and shared_ptr classes support move and copy; the IO and unique_ptr classes can be moved but not copied.

13.6.1. Rvalue References(C++11)

  • An rvalue reference is obtained by using && and it must be bound to an rvalue. It may be bound only to an object that is about to be destroyed. So, we are free to move resources from an rvalue reference to another object.
  • Like any reference, an rvalue reference is another name for an object. We can’t bind lvalue references to expressions that require a conversion, to literals, or to expressions that return an rvalue(§2.3.1); we can bind an rvalue reference to these kinds of expressions, but we can’t directly bind an rvalue reference to an lvalue.
int i = 42;int &r = i;             // ok: r refers to iint &&rr = i;               // error: can't bind an rvalue reference to an lvalueint &r2 = i * 42;           // error: i * 42 is an rvalueconst int &r3 = i * 42; // ok: we can bind a reference to const to an rvalueint &&rr2 = i * 42;     // ok: bind rr2 to the result of the multiplication
  • Functions that return lvalue references, along with the assignment, subscript, dereference, and prefix increment/decrement operators, are all expressions that return lvalues. We can bind an lvalue reference to the result of any of these expressions.
  • Functions that return a nonreference type, along with the arithmetic, relational, bitwise, and postfix increment/decrement operators, all yield rvalues. We can’t bind an lvalue reference to these expressions, but we can bind either an lvalue reference to const or an rvalue reference to such expressions.

Lvalues Persist; Rvalues Are Ephemeral

  • Lvalues have persistent state, whereas rvalues are either literals or temporary objects created in the course of evaluating expressions.
  • Because rvalue references can only be bound to temporaries, we know that
    • The referred-to object is about to be destroyed.
    • There can be no other users of that object.
    These facts mean that code that uses an rvalue reference is free to take over resources from the object to which the reference refers.

Variables Are Lvalues

  • A variable is an expression with one operand and no operator. Variable expressions are lvalues and we can’t bind an rvalue reference to a variable even if that variable was defined as an rvalue reference type.
int &&rr1 = 42; // ok: literals are rvaluesint &&rr2 = rr1;    // error: cannot bind ‘int’ lvalue to ‘int&&’

The Library move Function

  • We can cast an lvalue to its corresponding rvalue reference type and obtain an rvalue reference bound to an lvalue by calling move(defined in ). move uses facilities in §16.2.6(p. 690) to return an rvalue reference to its given object.
int &&rr3 = std::move(rr1); // ok
  • Calling move tells the compiler that we have an lvalue that we want to treat as if it were an rvalue. After a call to move, we can destroy a moved-from object and can assign a new value to it, but we can’t use the value of a moved-from object.
  • Code that uses move should use std::move, not move. Doing so avoids potential name collisions.

Exercises Section 13.6.1

Exercise 13.45

Distinguish between an rvalue reference and an lvalue reference.

  • Lvalue reference: reference that can bind to an lvalue. (Regular reference)
  • Rvalue reference: reference to an object that is about to be destroyed.

Exercise 13.46

Which kind of reference can be bound to the following initializers?

int f();vector<int> vi(100);int? r1 = f();int? r2 = vi[0];int? r3 = r1;int? r4 = vi[0] * f();
  • &&
  • &
  • &
  • &&

Exercise 13.47

Give the copy constructor and copy-assignment operator in your String class from exercise 13.44 in §13.5(p. 531) a statement that prints a message each time the function is executed.

  • See 48.

Exercise 13.48

Define a vector and call push_back several times on that vector. Run your program and see how often Strings are copied.

// Test reference to http://coolshell.cn/articles/10478.html#include <iostream>#include <string>#include <memory>#include <vector>using namespace std;class String{public:    String(string id2): String(id2, "")    {        cout << "Default constructor -> String("")\n";    }    String(string id2, const char *);    String(const String&);    String& operator=(const String&);    ~String();    const char *c_str() const    {        return elements;    }    size_t size() const    {        return end - elements;    }    size_t length() const    {        return end - elements - 1;    }private:    string id = "default";    pair<char*, char*> alloc_n_copy(const char*, const char*);    void range_initializer(const char*, const char*);    void free();    char *elements;    char *end;    allocator<char> alloc;};pair<char*, char*> String::alloc_n_copy(const char *b, const char *e){    char *str = alloc.allocate(e - b);    return {str, uninitialized_copy(b, e, str)};}void String::range_initializer(const char *first, const char *last){    pair<char*, char*> newstr = alloc_n_copy(first, last);    elements = newstr.first;    end = newstr.second;}void String::free(){    if (elements)    {        for(char *c = elements; c != end; ++c)        {            alloc.destroy(c);        }        alloc.deallocate(elements, end - elements);    }}String::String(string id2, const char *s){    char *sl = const_cast<char*>(s);    while(*sl)    {        ++sl;    }    range_initializer(s, ++sl);    id = id2;    cout << "Constructor: String(const char *s). id = " << id << "\n";}String::String(const String& rhs){    cout << "Copy Constructor: left = " << id << ", right = " << rhs.id << "\n";    range_initializer(rhs.elements, rhs.end);    id = rhs.id;}String& String::operator=(const String &rhs){    pair<char*, char*> newstr = alloc_n_copy(rhs.elements, rhs.end);    free();    elements = newstr.first;    end = newstr.second;    cout << "Copy Assignment=: left = " << id << ", right = " << rhs.id << "\n";    id = rhs.id;    return *this;}String::~String(){    free();    cout << "Destructor: id = " << id << "\n";}int main(){    String str1(string("str1"));    String str2(string("str2"), "gaoxiangnumber1");    String str3(str2);    String str4 = str3;    String str5(string("str5"));    str5 = str4;    String str6(string("str6"), "hello world!");    cout << "Create vector<String> vec;\n";    vector<String> vec(3, string("push"));    vec.push_back(str1);    vec.push_back(str4);    vec.push_back(str6);    return 0;}/*Output:Constructor: String(const char *s). id = str1Default constructor -> String()Constructor: String(const char *s). id = str2Copy Constructor: left = default, right = str2Copy Constructor: left = default, right = str2Constructor: String(const char *s). id = str5Default constructor -> String()Copy Assignment=: left = str5, right = str2Constructor: String(const char *s). id = str6Create vector<String> vec;Constructor: String(const char *s). id = pushDefault constructor -> String()Copy Constructor: left = default, right = pushCopy Constructor: left = default, right = pushCopy Constructor: left = default, right = pushDestructor: id = pushCopy Constructor: left = default, right = str1Copy Constructor: left = default, right = pushCopy Constructor: left = default, right = pushCopy Constructor: left = default, right = pushDestructor: id = pushDestructor: id = pushDestructor: id = pushCopy Constructor: left = default, right = str2Copy Constructor: left = default, right = str6Destructor: id = pushDestructor: id = pushDestructor: id = pushDestructor: id = str1Destructor: id = str2Destructor: id = str6Destructor: id = str6Destructor: id = str2Destructor: id = str2Destructor: id = str2Destructor: id = str2Destructor: id = str1*/

13.6.2. Move Constructor and Move Assignment

  • To enable move operations for our own types, we define a move constructor and a move-assignment operator. These members steal resources from their given object rather than copy them.
  • The move constructor has a parameter that is an rvalue reference to the class type, any additional parameters must all have default arguments. After moving resources, the move constructor must ensure that the moved-from object is left in a state such that destroying that object will be harmless and the original object must no longer point to those moved resources.
StrVec::StrVec(StrVec &&s) noexcept: // move won't throw any exceptions// member initializers take over the resources in selements(s.elements), first_free(s.first_free), cap(s.cap){    // leave s in a state in which it is safe to run the destructor    s.elements = s.first_free = s.cap = nullptr;}

Move Operations, Library Containers, and Exceptions

  • Because a move operation(move constructors and move assignment operators) executes by stealing resources, it ordinarily does not allocate any resources. So, move operations ordinarily will not throw any exceptions. When we write a move operation that can’t throw, we should inform the library of that fact. Unless the library knows that our move constructor won’t throw, it will do extra work to cater to the possibility that moving an object of our class type might throw.
  • noexcept(§18.1.4) is a way for us to promise that a function does not throw any exceptions. We specify noexcept on a function after its parameter list. In a constructor, noexcept appears between the parameter list and the : that begins the constructor initializer list. We must specify noexcept on both the declaration in the class header and on the definition if that definition appears outside the class.
  • We need to indicate that a move operation doesn’t throw because of two interrelated facts:
    1. Move operations are permitted to throw.
    2. The library containers provide guarantees as to what they do if an exception happens. For example, vector guarantees that if an exception happens when we call push_back, the vector itself will be left unchanged.
  • Let’s think about what happens inside push_back. push_back on a vector might require that the vector be reallocated. When a vector is reallocated, it moves the elements from its old space to new memory.
  • Moving an object generally changes the value of the moved-from object. If reallocation uses a move constructor and that constructor throws an exception after moving some but not all of the elements, there would be a problem: the moved-from elements in the old space would have been changed, and the unconstructed elements in the new space would not yet exist. So, vector would be unable to meet its requirement that the vector is left unchanged.
  • If vector uses the copy constructor and an exception happens, it can meet this requirement: while the elements are being constructed in the new memory, the old elements remain unchanged; if an exception happens, vector can free the space it allocated and return. The original vector elements still exist.
  • To avoid this problem, vector must use a copy constructor instead of a move constructor during reallocation unless it knows that the element type’s move constructor can’t throw an exception. If we want objects of our type to be moved rather than copied in circumstances such as vector reallocation, we must tell the library that our move constructor is safe to use by marking the move constructor(and move-assignment operator) noexcept.

Move-Assignment Operator

  • The move-assignment operator does the same work as the destructor and the move constructor. If move-assignment operator won’t throw any exceptions, we should make it noexcept. A move-assignment operator must guard against self-assignment.
StrVec::StrVec &operator=(StrVec &&rhs) noexcept{    if(this != &rhs) // direct test for self-assignment    {        free(); // free existing elements        elements = rhs.elements; // take over resources from rhs        first_free = rhs.first_free;        cap = rhs.cap;        // leave rhs in a destructible state        rhs.elements = rhs.first_free = rhs.cap = nullptr;    }    return *this;}
  • We check for self-assignment since rvalue for the right-hand operand can be the result of calling move. As in any other assignment operator, we must not free the left-hand resources before using those(possibly same) resources from the right-hand operand.

A Moved-from Object Must Be Destructible

  • Moving from an object does not destroy that object, so move operation must ensure that the moved-from object is in a state in which the destructor can be run.
  • Move operations also must guarantee that the object remains valid. A valid object is one that can safely be given a new value or used in ways that don’t depend on its current value.
  • Move operations have no requirements as to the value that remains in the moved-from object, so, programs should never depend on the value of a moved-from object.
  • Summary: After a move operation, the moved-from object must remain a valid, destructible object but users may make no assumptions about its value.

The Synthesized Move Operations

  • If we don’t declare our own copy constructor or copy-assignment operator, the compiler synthesizes these operations(§13.1.1 and §13.1.2). The copy operations are defined either to memberwise copy/assign the object or defined as deleted functions.
  • If a class defines its own copy constructor, copy-assignment operator, or destructor, the move constructor and move-assignment operator are not synthesized. When a class doesn’t have a move operation, the corresponding copy operation is used in place of move through function matching.
  • The compiler will synthesize a move constructor or a move-assignment operator only if the class doesn’t define any of its own copy-control members and if every nonstatic data member of the class can be can be moved constructed and move assigned, respectively. The compiler can move members of built-in type or class type if the member’s class has the corresponding move operation.
// the compiler will synthesize the move operations for X and hasXstruct X{    int i;      // built-in types can be moved    std::string s;  // string defines its own move operations};struct hasX{    X mem;      // X has synthesized move operations};X x, x2 = std::move(x);         // uses the synthesized move constructorhasX hx, hx2 = std::move(hx);   // uses the synthesized move constructor
  • A move operation is never implicitly defined as a deleted function. But if we define a move operation by using = default(§7.1.4), and the compiler is unable to move all the members, then the move operation will be defined as deleted.
  • Rules for when a synthesized move operation is defined as deleted:
    • move constructor is deleted if the class has a member that defines its own copy constructor but does not define a move constructor, or if the class has a member that doesn’t define its own copy operations and for which the compiler is unable to synthesize a move constructor. Similarly for move-assignment.
    • move constructor or move-assignment operator is deleted if the class has a member whose own move constructor or move-assignment operator is deleted or inaccessible.
    • move constructor is deleted if the destructor is deleted or inaccessible.
    • move-assignment operator is deleted if the class has a const or reference member.
// assume Y is a class that defines its own copy constructor but not move constructorstruct hasY{    hasY() = default;    hasY(hasY&&) = default;    Y mem; // hasY will have a deleted move constructor};hasY hy, hy2 = std::move(hy); // error: move constructor is deleted
  • Had hasY omitted the declaration of its move constructor, then the compiler would not synthesize the hasY move constructor at all.
  • If the class defines either move constructor and/or move-assignment operator, the synthesized copy constructor and copy-assignment operator for that class will be defined as deleted.

Rvalues Are Moved, Lvalues Are Copied …

  • When a class has both a move constructor and a copy constructor, the compiler uses function matching to determine which constructor to use(§6.4). Similarly for assignment.
  • In StrVec class the copy versions take a reference to const StrVec, so, they can be used on any type that can be converted to StrVec. The move versions take a StrVec&& and can be used only when the argument is a(nonconst) rvalue:
StrVec v1, v2;v1 = v2;                        // v2 is an lvalue; copy assignmentStrVec getVec(istream &);   // getVec returns an rvaluev2 = getVec(cin);               // getVec(cin) is an rvalue; move assignment
  • In the second assignment, we assign from the result of a call to getVec that is an rvalue. Both assignment operators are viable: calling the copy-assignment operator requires a conversion to const, whereas StrVec&& is an exact match. So, the second assignment uses the move-assignment operator.

…But Rvalues Are Copied If There Is No Move Constructor

  • If a class has a copy constructor but does not define a move constructor, the compiler will not synthesize the move constructor, so the class has no move constructor. When a class has no move constructor, function matching ensures that objects of that type are copied, even if we attempt to move them by calling move.
class Foo{public:    Foo() = default;    Foo(const Foo&);    // copy constructor    // Foo does not define a move constructor};Foo x;Foo y(x);               // copy constructor; x is an lvalueFoo z(std::move(x));        // copy constructor, because there is no move constructor
  • The call to move(x) in the initialization of z returns a Foo&& bound to x. The copy constructor is viable because we can convert a Foo&& to a const Foo&. Thus, the initialization of z uses the copy constructor for Foo.
  • Using the copy constructor in place of a move constructor is safe, similarly for the assignment operators. The copy constructor meets the requirements of the corresponding move constructor: It will copy the given object and leave that original object in a valid state.

Copy-and-Swap Assignment Operators and Move

  • If we add a move constructor to class HasPtr that defined a copy-and-swap assignment operator(§13.3), it will effectively get a move assignment operator:
class HasPtr{public:    // added move constructor    HasPtr(HasPtr &&p) noexcept: ps(p.ps), i(p.i)    {        p.ps = 0;    }    // assignment operator is both the move- and copy-assignment operator    HasPtr& operator=(HasPtr rhs)    {        swap(*this, rhs);        return *this;    }    // other members as in § 13.2.1 (p. 511)private:    string *ps;    int id;};
  • We add a move constructor that takes over the values from its given argument. The constructor body sets the pointer member of the given HasPtr to zero to ensure that it is safe to destroy the moved-from object. Nothing this function does can throw an exception so we mark it as noexcept(§13.6.2).
  • The assignment operator has a nonreference parameter, which means the parameter is copy initialized(§13.1.1). Depending on the type of the argument, copy initialization uses either the copy constructor or the move constructor: lvalues are copied and rvalues are moved. So, this single assignment operator acts as both the copy-assignment and move-assignment operator. Test:
class HasPtr{public:    HasPtr(const string &s = string()) : ps(new string(s)), id(0)    {        cout << "Constructor: ps = " << *ps << '\n';    }    HasPtr(const HasPtr &orig) : ps(new string(*(orig.ps))), id(orig.id)    {        cout << "Copy constructor: right = " << *ps << '\n';    }    // added move constructor    HasPtr(HasPtr &&p) noexcept: ps(p.ps), id(p.id)    {        p.ps = nullptr;        cout << "Move constructor: right = " << *ps << '\n';    }    // assignment operator is both the move- and copy-assignment operator    HasPtr& operator=(HasPtr rhs)    {        cout << "Before operator=. left = " << *ps << ", right = " << *rhs.ps << '\n';        delete ps;        ps = new string(*rhs.ps);        id = rhs.id;        return *this;    }    ~HasPtr()    {        cout << "Destructor: ps = " << (ps ? *ps : "nullptr") << '\n';        delete ps;    }private:    string *ps;    int id;};int main(){    HasPtr obj1("gao");    HasPtr obj2(obj1);    HasPtr obj3("xiang");    HasPtr obj4(move(obj3));    HasPtr obj5("number"), obj6("one");    cout << "AAA\n";    obj5 = obj1;    cout << "BBB\n";    obj6 = move(obj5);    return 0;}/*Output:Constructor: ps = gaoCopy constructor: right = gaoConstructor: ps = xiangMove constructor: right = xiangConstructor: ps = numberConstructor: ps = oneAAACopy constructor: right = gaoBefore operator=. left = number, right = gaoDestructor: ps = gaoBBBMove constructor: right = gaoBefore operator=. left = one, right = gaoDestructor: ps = gaoDestructor: ps = gaoDestructor: ps = nullptrDestructor: ps = xiangDestructor: ps = nullptrDestructor: ps = gaoDestructor: ps = gao*/

Advice: Updating the Rule of Three

  • All five copy-control members should be thought of as a unit: if a class defines any of these operations, it usually should define them all. Ordinarily, copying a resource entails some overhead. Classes that define the move constructor and move-assignment operator can avoid this overhead in circumstances where a copy isn’t necessary.

Move Operations for the Message Class(Omit)

Move Iterators(Omit)

Advice: Don’t Be Too Quick to Move

  • Because a moved-from object has indeterminate state, calling std::move on an object is a dangerous operation. When we call move, we must be certain that there can be no other users of the moved-from object.
  • Judiciously used inside class code, move can offer performance benefits. Casually used in ordinary user code, moving an object is likely to lead to hard-to-find bugs than to any improvement in the performance of the application.

Best Practices

  • Outside of class implementation code such as move constructors or move-assignment operators, use std::move only when you are certain that you need to do a move and the move is guaranteed to be safe.

Exercises Section 13.6.2

Exercise 13.49

Add a move constructor and move-assignment operator to your StrVec, String, and Message classes.

// For String:String::String(String&& s) noexcept :elements(s.elements), end(s.end){    s.elements = s.end = nullptr;}String& String::operator=(String&& rhs) noexcept{    if(this != &rhs)    {        free();        elements = rhs.elements;        end = rhs.end;        rhs.elements = rhs.end = nullptr;    }    return *this;}// For StrVec:StrVec::StrVec(StrVec&& s) noexcept:    elements(s.elements), first_free(s.first_free), cap(s.cap){    // leave s in a state in which it is safe to run the destructor.    s.elements = s.first_free = s.cap = nullptr;}StrVec& StrVec::operator=(StrVec&& rhs) noexcept{    if(this != &rhs)    {        free();        elements = rhs.elements;        first_free = rhs.first_free;        cap = rhs.cap;        rhs.elements = rhs.first_free = rhs.cap = nullptr;    }    return *this;}

Exercise 13.50

Put print statements in the move operations in your String class and rerun the program from exercise 13.48 in §13.6.1(p. 534) that used a vector to see when the copies are avoided.

String mov(){    String ret("world");    return ret; // first avoided}String s5 = mov(); // second avoided

Exercise 13.51

Although unique_ptrs can’t be copied, in §12.1.5(p. 471) we wrote a clone function that returned a unique_ptr by value. Explain why that function is legal and how it works.
§12.1.5: There is one exception to the rule that we cannot copy a unique_ptr: We can copy or assign a unique_ptr that is about to be destroyed. The common example is when we return a unique_ptr from a function.

unique_ptr<int> clone(int p){    // ok: explicitly create a unique_ptr<int> from int*    return unique_ptr<int>(new int(p));}

Alternatively, we can also return a copy of a local object:

unique_ptr<int> clone(int p){    unique_ptr<int> ret(new int (p));    // . . .    return ret;}
  • For such case, move semantics is expected rather than copy operation. That’s why a unique_ptr may be returned from a function by value. Reference.
  • The result of a call to clone is an rvalue, so it uses the move-assignment operator rather than copy-assignment operator. Thus, it is legal and can pretty work.

Exercise 13.52

Explain in detail what happens in the assignments of the HasPtr objects on page 541. In particular, describe step by step what happens to values of hp, hp2, and of the rhs parameter in the HasPtr assignment operator.

  • rhs parameter is nonreference, which means the parameter is copy initialized. Depending on the type of the argument, copy initialization uses either the copy constructor or the move constructor.
  • lvalues are copied and rvalues are moved. Thus, in hp = hp2;, hp2 is an lvalue, copy constructor used to copy hp2. In hp = std::move(hp2);, move constructor moves hp2.

Exercise 13.53

As a matter of low-level efficiency, the HasPtr assignment operator isn’t ideal. Explain why. Implement a copy-assignment and move-assignment operator for HasPtr and compare the operations executed in your new move-assignment operator versus the copy-and-swap version.

#include <iostream>#include <string>using namespace std;class HasPtr{public:    friend void swap(HasPtr&, HasPtr&);    HasPtr(const string& s = string());    HasPtr(const HasPtr& hp);    HasPtr(HasPtr&& p) noexcept;    HasPtr& operator=(HasPtr rhs);    /*    HasPtr& operator=(const HasPtr &rhs);    HasPtr& operator=(HasPtr &&rhs) noexcept;    */    ~HasPtr();private:    string* ps;    int i;};inline void swap(HasPtr& lhs, HasPtr& rhs){    swap(lhs.ps, rhs.ps);    swap(lhs.i, rhs.i);    cout << "call swap" << endl;}HasPtr::HasPtr(const string& s) : ps(new string(s)), i(0){    cout << "call constructor" << endl;}HasPtr::HasPtr(const HasPtr& hp) : ps(new string(*hp.ps)), i(hp.i){    cout << "call copy constructor" << endl;}HasPtr::HasPtr(HasPtr&& p) noexcept :ps(p.ps), i(p.i){    p.ps = 0;    cout << "call move constructor" << endl;}HasPtr& HasPtr::operator=(HasPtr rhs){    swap(*this, rhs);    return *this;}/*HasPtr& HasPtr::operator=(const HasPtr &rhs){    auto newp = new string(*rhs.ps);    delete ps;    ps = newp;    i = rhs.i;    cout << "call copy assignment" << endl;    return *this;}HasPtr& HasPtr::operator=(HasPtr &&rhs) noexcept{    if (this != &rhs)    {        delete ps;        ps = rhs.ps;        i = rhs.i;        rhs.ps = nullptr;        cout << "call move assignment" << endl;    }    return *this;}*/HasPtr::~HasPtr(){    cout << "call destructor" << endl;    delete ps;}int main(){    HasPtr hp1("hello"), hp2("World"), *pH = new HasPtr("World");    hp1 = hp2;    hp1 = move(*pH);}
  • Reference.

Exercise 13.54

What would happen if we defined a HasPtr move-assignment operator but did not change the copy-and-swap operator? Write code to test your answer.

error: ambiguous overload for 'operator=' (operand types are 'HasPtr' and 'std::remove_reference<HasPtr&>::typehp1 = std::move(*pH);^

13.6.3. Rvalue References and Member Functions

  • Member functions other than constructors and assignment can benefit from providing both copy and move versions. Such move-enabled members typically use the same parameter pattern as the copy/move constructor and the assignment operators: one version takes an lvalue reference to const, the other takes an rvalue reference to nonconst.
  • The library containers that define push_back provide two versions: one has an rvalue reference parameter and the other a const lvalue reference. Assuming X is the element type, these containers define:
void push_back(const X&);   // copy: binds to any kind of Xvoid push_back(X&&);        // move: binds only to modifiable rvalues of type X
  • We can pass any object that can be converted to type X to the first version of push_back. This version copies data from its parameter.
    We can pass only an rvalue that isn’t const to the second version. This version is an exact match(and a better match) for nonconst rvalues and will be run when we pass a modifiable rvalue(§13.6.2). This version is free to steal resources from its parameter.
  • There is no need to define operation that take const X&& or X&.
    1. In order to steal from the rvalue reference argument, it must not be const.
    2. Copying from an object should not change the object being copied, so there is usually no need to define a version that take a X& parameter.
  • We give StrVec class a second version of push_back:
class StrVec{public:    void push_back(const std::string&); // copy the element    void push_back(std::string&&); // move the element    // other members as before};// unchanged from the original version in § 13.5 (p. 527)void StrVec::push_back(const string& s){    chk_n_alloc(); // ensure that there is room for another element    // construct a copy of s in the element to which first_free points    alloc.construct(first_free++, s);}void StrVec::push_back(string &&s){    chk_n_alloc(); // reallocates the StrVec if necessary    alloc.construct(first_free++, std::move(s));}
  • The difference is that the rvalue reference version calls move to pass its parameter to construct. The construct function uses the type of its second and subsequent arguments to determine which constructor to use. Because move returns an rvalue reference, the type of the argument to construct is string&&. Therefore, the string move constructor will be used to construct a new last element.
  • When we call push_back, the type of the argument determines whether the new element is copied or moved into the container:
StrVec vec;             // empty StrVecstring s = "some string or another";vec.push_back(s);       // calls push_back(const string&)vec.push_back("done");  // calls push_back(string&&)

Rvalue and Lvalue Reference Member Functions

  • Ordinarily, we can call a member function on an object, regardless of whether that object is an lvalue or an rvalue. For example:
string s1 = "a value", s2 = "another";auto n =(s1 + s2).find('a');s1 + s2 = "wow!";
  • We called the find member(§9.5.3) on the string rvalue that results from adding two strings; we assign to the rvalue result of concatentating these strings.
  • Prior to C++11, there was no way to prevent such usage. In order to maintain backward compatability, the library classes continue to allow assignment to rvalues. But we can force the left-hand operand(i.e., the object to which this points) to be an lvalue. We indicate the lvalue/rvalue property of this by placing a reference qualifier after the parameter list.
class Foo{public:    Foo &operator=(const Foo&) &; // may assign only to modifiable lvalues    // other members of Foo};Foo &Foo::operator=(const Foo &rhs) &{    // do whatever is needed to assign rhs to this object    return *this;}
  • The reference qualifier can be either & or &&, indicating that this can point to an rvalue or lvalue, respectively. Like the const qualifier, a reference qualifier may appear only on a nonstatic member function and must appear in both the declaration and definition of the function.
  • We may run a function qualified by & only on an lvalue and may run a function qualified by && only on an rvalue.
Foo &retFoo();  // returns a reference; a call to retFoo is an lvalueFoo retVal();       // returns by value; a call to retVal is an rvalueFoo i, j;           // i and j are lvaluesi = j;          // ok: i is an lvalueretFoo() = j;       // ok: retFoo() returns an lvalueretVal() = j;       // error: retVal() returns an rvaluei = retVal();       // ok: we can pass an rvalue as the right-hand operand to assignment
  • A function can be both const and reference qualified, the reference qualifier must follow the const qualifier.
class Foo{public:    Foo someMem() & const;      // error: const qualifier must come first    Foo anotherMem() const &;   // ok: const qualifier comes first};

Overloading and Reference Functions

  • We may overload a function by its reference qualifier and by whether it is a const member. We give Foo a vector member and a function named sorted that returns a copy of the Foo object in which the vector is sorted:
class Foo{public:    Foo sorted() &&;        // may run on modifiable rvalues    Foo sorted() const &;   // may run on any kind of Foo    // other members of Fooprivate:    vector<int> data;};// this object is an rvalue, so we can sort in placeFoo Foo::sorted() &&{    sort(data.begin(), data.end());    return *this;}// this object is either const or it is an lvalue; either way we can't sort in placeFoo Foo::sorted() const &{    Foo ret(*this); // make a copy    sort(ret.data.begin(), ret.data.end()); // sort the copy    return ret; // return the copy}
    1. When we run sorted on an rvalue, it is safe to sort the data member directly: the object is an rvalue, which means it has no other users, so we can change the object itself.
    2. When we run sorted on a const rvalue or on an lvalue, we can’t change this object, so we copy data before sorting it.
  • Overload resolution uses the lvalue/rvalue property of the object that calls sorted to determine which version is used.
retVal().sorted();      // retVal() is an rvalue, calls Foo::sorted() &&retFoo().sorted();      // retFoo() is an lvalue, calls Foo::sorted() const &
    1. When we define const member functions, we can define two versions that differ only in that one is const qualified and the other is not.
    2. When we define two or more members that have the same name and the same parameter list, we must provide a reference qualifier on all or none of those functions.
class Foo{public:    Foo sorted() &&;    Foo sorted() const;         // error: must have reference qualifier    // Comp is the function type(§6.7) that can be used to compare int values    using Comp = bool(const int&, const int&);    Foo sorted(Comp*);          // ok: different parameter list    Foo sorted(Comp*) const;        // ok: neither version is reference qualified};

Exercises Section 13.6.3

Exercise 13.55

Add an rvalue reference version of push_back to your StrBlob.

void push_back(string &&s){    data->push_back(std::move(s));}

Exercise 13.56

What would happen if we defined sorted as:

Foo Foo::sorted() const &{    Foo ret(*this);    return ret.sorted();}
  • Because the local variable ret is an lvalue, so when we call ret.sorted(), we are not calling Foo Foo::sorted() &&, but Foo Foo::sorted() const & instead. As a result, the code will be trapped into a recursion and causes a stack overflow.

Exercise 13.57

What if we defined sorted as:

Foo Foo::sorted() const &{    return Foo(*this).sorted();}
  • ok, it will call the move version.

Exercise 13.58

Write versions of class Foo with print statements in their sorted functions to test your answers to the previous two exercises.

#include <vector>#include <iostream>#include <algorithm>using namespace std;class Foo{public:    Foo sorted() &&;    Foo sorted() const &;private:    vector<int> data;};Foo Foo::sorted() &&{    sort(data.begin(), data.end());    cout << "&&" << endl; // debug    return *this;}Foo Foo::sorted() const &{//    Foo ret(*this);//    sort(ret.data.begin(), ret.data.end());//    return ret;    cout << "const &" << endl; // debug//    Foo ret(*this);//    ret.sorted();     // Exercise 13.56//    return ret;    return Foo(*this).sorted(); // Exercise 13.57}int main(){    Foo().sorted(); // call "&&"    Foo f;    f.sorted(); // call "const &"}

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

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

0 0
原创粉丝点击