13-Copy Control

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 body

private:
    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 initialization
string b(a);                // direct initialization
string c = b;               // copy initialization
string d = "xiang";     // copy initialization
string 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 initialization
vector<int> v2 = 10;    // error: constructor that takes a size is explicit
void f(vector<int>);    // f's parameter is copy initialized
f(10);              // error: can't use an explicit constructor to copy an argument
f(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 = global
Constructor: id = obj
Before call
Copy constructor: id = obj
Enter call, before `Point local = arg;`
Copy constructor: id = obj
After `Point local = arg;`, before `Point *heap = new Point(global);`
Copy constructor: id = global
After `Point *heap = new Point(global);`, before `*heap = local;`
After `*heap = local;`, before `Point pa[ 4 ] = { local, *heap };`
Copy constructor: id = obj
Copy constructor: id = obj
Constructor: id = default 1
Constructor: id = default 2
After `Point pa[ 4 ] = { local, *heap };`, before `return *heap;`
Copy constructor: id = obj
Destructor: id = default 2
Destructor: id = default 1
Destructor: id = obj
Destructor: id = obj
Destructor: id = obj
Destructor: id = obj
Destructor: id = obj
After call
Destructor: id = obj
Destructor: 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 operator
Sales_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 = obj1
X(const X&): id = obj1
Fun1(X): id = obj1
~X(): id = obj1
Fun2(X&): id = obj1
X(): id = obj2
X(): id = obj3
X(const X&): id = obj3
X(const X&): id = obj3
X(const X&): id = obj3
X(const X&): id = obj3
X(const X&): id = obj3
~X(): id = obj3
~X(): id = obj2
EXIT
~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 freed
HasPtr 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 = 1
1
1
1

###1###
numbered2(): cnt = 2
numbered2(const numbered2 &): old num = 2, new num = 3
numbered2(const numbered2 &): old num = 1, new num = 4
numbered2(const numbered2 &): old num = 4197328, new num = 5
5
numbered2(const numbered2 &): old num = 5, new num = 6
6
numbered2(const numbered2 &): old num = 6, new num = 7
7

###7###
2
3
4

###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 construct
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值