《C++ Primer第五版》读书笔记(11)-COPY CONTROL

Each class defines a new type and defines the operations that objects of that type can perform.


When we define a class,we specify—explicitly or implicitly—what happens when objects of that class type are copied, moved, assigned, and destroyed. A class controls these operations by defining five special member functions: copy constructor, copy assignment operator, move constructor, move-assignment operator, and destructor.
Collectively, we’ll refer to these operations as copy control.


Copy control is an essential part of defining any C++ class. Programmers new to C++ are often confused by having to define what happens when objects are copied, moved, assigned, or destroyed. This confusion is compounded because if we do not explicitly define these operations, the compiler defines them for us—although the compiler-defined versions might not behave as we intend.


13.1 COPY, ASSIGN, AND DESTROY
13.1.1 COPY CONSTRUCTOR & COPY INITILIAZATION


A constructor is the copy constructor if its first parameter is a reference to the class type and any additional parameters have default values:
class Foo {
public:
   Foo();  // default constructor
   Foo(const Foo&);  // copy constructor
  // ...
};


The first parameter must be a reference type. That parameter is almost always a reference to const, although we can define the copy constructor to take a reference to nonconst. The copy constructor is used implicitly in several circumstances. Hence, the copy constructor usually should not be explicit.

The Synthesized Copy Constructor


When we do not define a copy constructor for a class, the compiler synthesizes one for us. Unlike the synthesized default constructor, a copy constructor is synthesized even if we define other constructors.


The compiler 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: Members of class type are copied by the copy constructor for that class; members of built-in type are copied directly. Although we cannot directly copy an array, the synthesized copy constructor copies members of array type by copying each element. Elements of class type are copied by using the elements’ copy constructor.

Copy Initialization

string dots(10, ‘.‘);  // direct initialization
string s(dots);  // direct initialization
string s2 = dots;  // copy initialization
string null_book = "9-999-99999-9"; // copy initialization
string nines = string(100, ‘9‘);  // copy initialization


When we use direct initialization, we are asking the compiler to use ordinary function matching (§ 6.4, p. 233) to select the constructor that best matches the arguments we provide. When we use copy initialization, we are asking the compiler to copy the right-hand operand into the object being created, converting that operand if necessary.


Copy initialization ordinarily uses the copy constructor. However, as we’ll see in §13.6.2(p. 534), if a class has a move constructor, then copy initialization sometimes uses the move constructor instead of the copy constructor.笔者:在C++11之前只有copy,所以叫copy initialization. C++11为了提高效率增加了move,可是仍然叫copy initilization,毕竟就是个等号初始化嘛,总不至于起两个名字吧.


Copy initialization happens not only when we define variables using an =, but also when we
?Pass an object as an argument to a parameter of nonreference type
?Return an object from a function that has a nonreference return type
?Brace initialize the elements in an array or the members of an aggregate class


Some class types also use copy initialization for the objects they allocate. For example,the library containers copy initialize their elements when we initialize the container, or when we call an insert or push member. By contrast, elements created by an emplace member are direct initialized


Parameters and Return Values


During a function call, parameters that have a nonreference type are copy initialized(§ 6.2.1, p. 209). Similarly, when a function has a nonreference return type, the return value is used to copy initialize the result of the call operator at the call site.
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 the call would never succeed—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.

Constraints on Copy Initialization
As we’ve seen, whether we use copy or direct initialization matters if we use an initializer that requires conversion by an explicit constructor.
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
Directly initializing v1is fine, but the seemingly equivalent copy initialization of v2 is an error, because the vector constructor that takes a single size parameter is explicit. For the same reasons that we cannot copy initialize v2, we cannot implicitly use an explicit constructor when we pass an argument or return a value from a function. If we want to use an explicit constructor, we must do so explicitly, as in the last line of the example above.

The Compiler Can Bypass the Copy Constructor
During copy initialization, the compiler is permitted (but not obligated) 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
However,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.


13.1.2 THE COPY-ASSIGNMENT OPERATOR


Overloaded operators are functions that have the name operator followed by the symbol for the operator being defined. Hence, the assignment operator is a function named operator=.
Some operators, assignment among them, 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, p. 257). The right-hand operand in a binary operator, such as assignment, 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, p. 145), assignment operators usually return a reference to their left-hand operand. It is also worth noting that 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


Just as it does for the copy constructor, the compiler generates a synthesized copy assignment operator for a class if the class does not define its own.
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
}

13.1.3 THE DESTRUCTOR


Destructors do whatever work is needed to free the resources used by an object and destroy the nonstatic data members of the object.
class Foo {
public:
   ~Foo();  // destructor
   // ...
};


Because it takes no parameters, it cannot be overloaded. There is always only one destructor for a given class.

What a Destructor Does
In a constructor, members are initialized before the function body is executed, and members are initialized in the same order as they appear in the class. In a destructor, the function body is executed first and then the members are destroyed. Members are destroyed in reverse order from the order in which they were initialized.
The implicit destruction of a member of built-in pointer type does not delete the object to which that pointer points. Unlike ordinary pointers, the smart pointers (§ 12.1.1, p. 452) are class types and have destructors.


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 the new standard, a class can also define a move constructor and move-assignment operator. There is no requirement that we define all of these operations: We can define one or two of them without having to define all of them. However, ordinarily these operations should be thought of as a unit. In general, it is unusual to need one without needing to define them all.


Classes That Need Destructors Need Copy and Assignment


One rule of thumb to use when you decide whether a class needs to define its own versions of the copy-control members is to decide first whether the class needs a destructor. Often, the need for a destructor is more obvious than the need for the copy constructor or assignment operator. If the class needs a destructor, it almost surely needs a copy constructor and copy-assignment operator as well.

Classes That Need Copy Need Assignment, and Vice Versa
A second rule of thumb: If a class needs a copy constructor, it almost surely needs a copy-assignment operator. And vice versa—if the class needs an assignment operator, it almost surely needs a copy constructor as well.
Nevertheless, needing either the copy constructor or the copy-assignment operator does not (necessarily) indicate the need for a destructor.
As an example, consider a class that gives each object its own, unique serial number. Such a class would need a copy constructor to generate a new, distinct serial number for the object being created. That constructor would copy all the other data members from the given object. This class would also need its own copy-assignment operator to avoid assigning to the serial number of the left-hand object. However, this class would have no need for a destructor.

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.

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 (just as is any other member function defined in the body of the class). If we do not want the synthesized member to be an inline function, we can specify = default on the member’s definition, as we do in the definition of the copy-assignment operator.


13.1.6 PREVENTING COPIES
For example, the iostream classes prevent copying to avoid letting multiple objects write to or read from the same IO buffer. It might seem that we could prevent copies by not defining the copy-control members. However, this strategy doesn’t work: If our class doesn’t define these operations, the compiler will synthesize them.

Under the new standard, we can prevent copies by defining the copy constructor and copy-assignment operator as deleted functions. A deleted function is one that is declared but may not be used in any other way. We indicate that we want to define a function as deleted by following its parameter list with = delete:
struct NoCopy {
NoCopy() = default;  // use the synthesized default constructor
NoCopy(const NoCopy&) = delete;  // no copy
NoCopy &operator=(const NoCopy&) = delete; // no assignment
~NoCopy() = default;  // use the synthesized destructor
// other members
};

The =delete signals to the compiler (and to readers of our code) that we are intentionally not defining these members.
Unlike = default, = delete must appear on the first declaration of a deleted function. This difference follows logically from the meaning of these declarations. A defaulted member affects only what code the compiler generates; hence the = default is not needed until the compiler generates code. On the other hand, the compiler needs to know that a function is deleted in order to prohibit operations that attempt to use it.
Also unlike = default, we can specify = delete on any function (we can use = default only on the default constructor or a copy-control member that the compiler can synthesize). Although the primary use of deleted functions is to suppress the copy-control members, deleted functions are sometimes also useful when we want to guide the function-matching process.

The Destructor Should Not be a Deleted Member



The Copy-Control Members May Be Synthesized as Deleted
As we’ve seen, if we do not define the copy-control members, the compiler defines them for us. Similarly, if a class defines no constructors, the compiler synthesizes a default constructor for that class (§ 7.1.4, p. 262). For some classes, the compiler defines these 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 is deleted or inaccessible. It is also deleted if the class has a member with a deleted or inaccessible destructor.
?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, p. 73); 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 cannot be default constructed, copied, assigned, or destroyed, then the corresponding member will be a deleted function.

It should not be surprising that the compiler will not synthesize a default constructor for a class with a reference member or a const member that cannot be default constructed. Nor should it be surprising that a class with a const member cannot use the synthesized copy-assignment operator: After all, that operator attempts to assign to every member. It is not 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. It would not refer to the same object as the right-hand operand. Because this behavior is unlikely to be desired, the synthesized copy-assignment operator is defined as deleted if the class has a reference member.


private Copy Control
Prior to the new standard, classes prevented copies by declaring their copy constructor and copy-assignment operator as private:
class PrivateCopy {
// no access specifier; following members are private by default; see § 7.2 (p. 268)
// copy control is private and so is inaccessible to ordinary user code
PrivateCopy(const PrivateCopy&);
PrivateCopy &operator=(const PrivateCopy&);
// other members
public:
PrivateCopy() = default; // use the synthesized default constructor
~PrivateCopy(); // users can define objects of this type but not copy them
};


Because the copy constructor and copy-assignment operator are private, user code will not be able to copy such objects. However, friends and members of the class can still make copies.  To prevent copies by friends and members, we declare these members as private but do not define them. An attempt to use an undefined member results in a link-time failure. By declaring (but not defining) a private copy constructor, we can forestall any attempt to copy an object of the class type.

Best Practices: Classes that want to prevent copying should define their copy constructor and copy-assignment operators using = delete rather than making those members private.


13.2 COPY CONTROL AND RESOURCE MANAGEMENT

We have two choices: We can define the copy operations to make the class behave like a value or like a pointer.
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. Changes made to the copy have no effect on the original, and vice versa.
Classes that act like pointers share state. When we copy objects of such classes, the copy and the original use the same underlying data. Changes made to the copy also change the original, and vice versa.


Of the library classes we’ve used, the library containers and string class have valuelike behavior. Not surprisingly, the shared_ptr class provides pointerlike behavior, as does our StrBlobclass (§ 12.1.1, p. 456). The IO types and unique_ptr do not allow copying or assignment, so they provide neither valuelike nor pointerlike behavior.


13.3 SWAP


In addition to defining the copy-control members, classes that manage resources often also define a function named swap. Defining swap is particularly important for classes that we plan to use with algorithms that reorder elements. Such algorithms call swap whenever they need to exchange two elements.
Unlike the copy-control members, swap is never necessary. However, defining swap can be an important optimization for classes that allocate resources.

class HasPtr{
                friend voidswap(HasPtr&, HasPtr&);
                // other members as in §13.2.1 (p. 511)
};

inline voids wap(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
}


There is one important subtlety in this code: Although it doesn’t matter in this particular case, it is essential that swap functions call swap and not std::swap. In the HasPtr function, the data members have built-in types. Thereis no type-specific version of swap for the built-in types. In this case, these calls will invoke the library std::swap.However,if a class has a member thathas its own type-specific swapfunction, calling std::swap would be a mistake. Each call to swap must be unqualified. That is, each call should be to swap, not std::swap. if there is a type-specific version of swap, that version will be a better match than the one defined in std. As a result, if there is a type-specific version of swap, calls to swap will match that type specific version. If there is no type-specific version, then—assuming there is a using declaration for swap in scope—calls to swap will use the version in std.

Classes that define swap often use swap to define their assignment operator. These operators use a technique known as copy and swap.
// note rhs is passed by value, which means the HasPtr copy constructor
// copies the string in the right-hand operand into rhs
HasPtr& 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
}


13.4 CLASSES THAT MANAGE DYNAMIC MEMORY

We can avoid copying the strings by using two facilities introduced by the new library. First, several of the library classes, including string, define so-called “move constructors.” The details of how the string move constructor works—like any other detail about the implementation—are not disclosed. However, we do know that move constructors typically operate by “moving” resources from the given object to the object being constructed.
The second facility we’ll use is a library function named move, which is defined in the utility header.


13.5 MOVING OBJECTS


One of the major features in the new standard is the ability to move rather than copy an object.In some of these circumstances, an object is immediately destroyed after it is copied. In those cases, moving, rather than copying, the object can provide a significant performance boost.
The library containers, string, and shared_ptr classes support move as well as copy. The IO and unique_ptr classes can be moved but not copied.

13.5.1 RVALUE REFERENCES
To support move operations, the new standard introduced a new kind of reference, an rvalue reference. An rvalue reference is a reference that must be bound to an rvalue. An rvalue reference is obtained by using &&rather than &. As we’ll see, rvalue references have the important property that they may be bound only to an object that is about to be destroyed. As a result, we are free to “move” resources from an rvalue reference to another object.
int i = 42;
int &r = i;  // ok: r refers to i
int &&rr = i;    // error: cannot bind an rvalue reference to an lvalue
int &r2 = i * 42;  // error: i * 42 is an rvalue

const int &r3 = i * 42; // ok: we can bind a reference to const to an rvalue
int &&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 examples of 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 cannot bind an lvalue reference to these expressions, but we can bind either an lvalue reference to const or an rvalue reference to such expressions.

Looking at the list of lvalue and rvalue expressions, it should be clear that lvalues and rvalues differ from each other in an important manner: 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

Rvalue references refer to objects that are about to be destroyed. Hence, we can “steal” state from an object bound to an rvalue reference.

Variables Are Lvalues
Variable expressions are lvalues. As a consequence, we cannot bind an rvalue reference to a variable defined as an rvalue reference type:
int &&rr1 = 42;  // ok: literals are rvalues
int &&rr2 = rr1;  // error: the expression rr1 is an lvalue!
It should not be surprising that a variable is an lvalue. After all, a variable persists until it goes out of scope.

The Library move Function
Although we cannot directly bind an rvalue reference to an lvalue, we can explicitly cast an lvalue to its corresponding rvalue reference type. We can also obtain an rvalue reference bound to an lvalue by calling a new library function named move, which is defined in the utility header.
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. It is essential to realize that the call to move promises that we do not intend to use rr1 again except to assign to it or to destroy it. After a call to move, we cannot make any assumptions about the value of the moved-from object.

13.6 MOVE CONSTRUCTOR AND MOVE ASSIGNMENT
Like the copy constructor, the move constructor has an initial parameter that is a reference to the class type. Differently from the copy constructor, the reference parameter in the move constructor is an rvalue reference.

StrVec::StrVec(StrVec &&s) noexcept  // move won‘t throw anyexceptions
// member initializers take over the resources in s
: 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;
}

Having taken over the memory from its argument, the constructor body sets the pointers in the given object to nullptr.
After an object is moved from, that object continues to exist. Eventually, the moved-from object will be destroyed, meaning that the destructor will be run on that object.

Move Operations, Library Containers, and Exceptions
Because a move operation executes by “stealing” resources, it ordinarily does not itself allocate any resources. As a result, move operations ordinarily will not throw any exceptions. When we write a move operation that cannot throw, we should inform the library of that fact.
One way inform the library is to specify noexcepton our constructor. We’ll cover noexcept, which was introduced by the new standard. noexcept is a way for us to promise that a function does not throw any exceptions.

Move-Assignment Operator
StrVec&StrVec::operator=(StrVec &&rhs) noexcept
{
// direct test for self-assignment
if (this != &rhs) {
   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;
}

A Moved-from Object Must Be Destructible
Differently from the copy operations, for some classes the compiler does not synthesize the move operations at all.In particular, if a class defines its own copy constructor, copy-assignment operator, or destructor, the move constructor and move assignment operator are not synthesized. As a result, some classes do not have a move constructor or a move-assignment operator. When a class doesn’t have a move operation, the corresponding copy operation is used in place of move through normal 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 moved. The compiler can move members of built-in type. It can also move members of a class type if the member’s class has the corresponding move operation:
// the compiler will synthesize the move operations for X and hasX
struct 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 constructor
hasX hx, hx2 = std::move(hx); // uses the synthesized move constructor
Unlike the copy operations, a move operation is never implicitly defined as a deleted function. However, if we explicitly ask the compiler to generate a move operation by using = default(§ 7.1.4, p. 264), and the compiler is unable to move all the members, then the move operation will be defined as deleted.

Rvalues Are Moved, Lvalues Are Copied ...
...But Rvalues Are Copied If There Is No Move Constructor

Advice:Updating the Rule of Three
All five copy-control members should be thought of as a unit: Ordinarily, if a class defines any of these operations, it usually should define them all. As we’ve seen, some classes must define the copy constructor, copy-assignment operator, and destructor to work correctly (§ 13.1.4, p. 504). Such classes typically have a resource that the copy members must copy. Ordinarily, copying a resource entails some amount of overhead. Classes that define the move constructor and move-assignment operator can avoid this overhead in those circumstances where a copy isn’t necessary.

Move Iterators
Instead,the new library defines a move iterator adaptor (§ 10.4, p. 401). A move iterator adapts its given iterator by changing the behavior of the iterator’s dereference operator. Ordinarily, an iterator dereference operator returns an lvalue reference to the element. Unlike other iterators, the dereference operator of a move iterator yields an rvalue reference.
We transform an ordinary iterator to a move iterator by calling the library make_move_iterator function. This function takes an iterator and returns a move iterator.
All of the original iterator’s other operations work as usual. Because these iterators support normal iterator operations, we can pass a pair of move iterators to an algorithm. In particular, we can pass move iterators to uninitialized_copy.
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 absolutely certain that there can be no other users of the moved-from object.


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‘);
Here,we called the find member (§ 9.5.3, p. 364) on the string rvalue that results from adding two strings.
We place a reference qualifier after the parameter list to indicate the lvalue/rvalue property of this.
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 may 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 lvalue
Foo retVal();  // returns by value; a call to retVal is an rvalue
Foo i, j;  // i and j are lvalues
i = j;  // ok: i is an lvalue
retFoo() = j;  // ok: retFoo() returns an lvalue
retVal() = j;  // error: retVal() returns an rvalue
i = retVal();  // ok: we can pass an rvalue as the right-hand operand to assignment
class Foo {
public:
   Foo someMem() & const;  // error: const qualifier must come first
   Foo anotherMem() const &; // ok: const qualifier comes first
};


Overloading and Reference Functions
Just as we can overload a member function based on whether it is const, we can also overload a function based on its reference qualifier. Moreover, we may overload a function by its reference qualifier and by whether it is a const member.
class Foo {
public:
   Foo sorted() &&;  // may run on modifiable rvalues
   Foo sorted() const &;  // may run on any kind of Foo
  // other members of Foo
private:
   vector<int> data;
};
// this object is an rvalue, so we can sort in place
Foo 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 place
Foo Foo::sorted() const & {
   Foo ret(*this);  // make a copy
   sort(ret.data.begin(), ret.data.end()); // sort the copy
  return ret;  // return the copy
}

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. 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.
retVal().sorted();// retVal() is an rvalue, calls Foo::sorted() &&
retFoo().sorted(); // retFoo() is an lvalue, calls Foo::sorted() const &
When we define const memeber functions, we can define two versions that differ only in that one is const qualified and the other is not. There is no similar default for reference qualified functions. 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 type alias for the function type (see § 6.7 (p. 249))
  // 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
};


《C++ Primer第五版》读书笔记(11)-COPY CONTROL,布布扣,bubuko.com

《C++ Primer第五版》读书笔记(11)-COPY CONTROL

上一篇:Python学习笔记三(文件与异常)


下一篇:VS C++ 从一个窗口创建另一个窗口