Copy Control

As we see in chapter 3, each class defines a new type and defines the operations that objects of that type can perform. In that chapter, we also learned that classes can define constructors, which control what happens when objects of the class type are created.
In this chapter we’ll learn how classes can control what happens when objects of the class type are copied, assigned, moved, or destroyed. Classes control these actions through special member functions: the copy constructor, move constructor, copy-assignment operator, move-assignment operator, and destructor.

Copy, Assign, and Destroy

The Copy Constructor

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 Temp {
public:
    Temp() = default; // default constructor
    Temp(const Temp&);// copy constructor
};

For reasons we’ll explain shortly, 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.

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

As an example, the synthesized copy constructor for our Sales_data class is equivalent to:

class Sales_data {
public:
    // other members and constructors as before
    // declaration equivalent to the synthesized copy constructor
    //copy constructors
    Sales_data(const Sales_data& org) :
        bookNo(org.bookNo), units_sold(org.units_sold), revenue(org.revenue) {
        //empty
    }
}

We are now in a position to fully understand the differences between direct initialization and copy initialization:

  1. When we use direct initialization, we are asking the compiler to use ordinary function matching to select the constructor that best matches the arguments we provide.
  2. 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 happens not only when we define variables using an =, but also when we:

  1. Pass an object as an argument to a parameter of nonreference type
  2. Return an object from a function that has a nonreference return type
  3. 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.

The Copy-Assignment Operator

As with the copy constructor, the compiler synthesizes a copy-assignment operator if the class does not define its own. Before we look at the synthesized assignment operator, we need to know a bit about overloaded operators, which we cover in detail in next chapter.

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=. Like any other function, an operator function has a return type and a parameter list.

The parameters in an overloaded operator represent the operands of the 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. 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:

Temp& operator=(const Temp& org);

To be consistent with assignment for the built-in types, 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.

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

Temp& Temp::operator=(const Temp& org) {
    data = org.data + 2;
    return *this;
}
#include"Temp.h"

int main() {
    Temp p(10); //p.data = 10
    Temp q = p; //q.data = 11
    Temp r;
    r = p;      //r.data = 12
    return 0;
}

The Destructor

The destructor operates inversely to the constructors: 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:

class Temp{
public:
    ~Temp(); // destructor
}

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

Just as a constructor has an initialization part and a function body, a destructor has a function body and a destruction part. 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 function body of a destructor does whatever operations the class designer wishes to have executed subsequent to the last use of an object. Typically, the destructor frees resources an object allocated during its lifetime.

In a destructor, there is nothing akin to the constructor initializer list to control how members are destroyed; the destruction part is implicit. What happens when a member is destroyed depends on the type of the member. Members of class type are destroyed by running the member’s own destructor. The built-in types do not have destructors, so nothing is done to destroy members of built-in type.

The destructor is used automatically whenever an object of its type is destroyed:

  1. Variables are destroyed when they go out of scope.
  2. Members of an object are destroyed when the object of which they are a part is destroyed.
  3. Elements in a container—whether a library container or an array—are destroyed when the container is destroyed.
  4. Dynamically allocated objects are destroyed when the delete operator is applied to a pointer to the object.
  5. Temporary objects are destroyed at the end of the full expression in which the temporary was created.

The Rule of Three/Five

As we’ve seen, there are three basic operations to control copies of class objects: the copy constructor, copy-assignment operator, and destructor. Moreover, in section 6, 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.

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.

As a example, we need allocate allocates dynamic memory to save a string in the class, when we call constructor. We also need free the memory when call destructor.

Temp::~Temp() {
    delete s;
}

In this version of the class, the memory allocated in the constructor will be freed when a object is destroyed. Unfortunately, we have introduced a serious bug! This version of the class uses the synthesized versions of copy and assignment. Those functions copy the pointer member, meaning that multiple objects may be pointing to the same memory:

#pragma once
#include <string>
class Temp {
public:
    Temp() = default; // default constructor
    Temp(int num) :data(num), s(new std::string(10, 'A')) {

    }

    ~Temp(); // destructor
    const int a = 10;

private:
    int data;
    std::string* s;
};
Temp::~Temp() {
    delete s;
}
#include"Temp.h"

int main() {
    Temp p(10); //p->s = AAAAAAAAAA
    Temp q = p; //q->s = AAAAAAAAAA
    // p.s and q.s point to same address
    return 0;
}

This code will delete that pointer twice.

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

Preventing Copies

Although most classes should (and generally do) define a copy constructor and a copy-assignment operator, for some classes, there really is no sensible meaning for these operations. In such cases, the class must be defined so as to prevent copies or assignments from being made. 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:

#pragma once
class NoCopy {
public:
    NoCopy() = default;
    ~NoCopy() = default;

    NoCopy(const NoCopy&) = delete; // no copy
private:
    int a = 10;
};

It is worth noting that we did not delete the destructor. If the destructor is deleted, then there is no way to destroy objects of that type.

Copy Control and Resource Management

Ordinarily, classes that manage resources that do not reside in the class must define the copy-control members. As we saw in previous, such classes will need destructors to free the resources allocated by the object. Once a class needs a destructor, it almost surely needs a copy constructor and copy-assignment operator as well.

In order to define these members, we first have to decide what copying an object of our type will mean. In general, 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 value like 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.

To illustrate these two approaches, we’ll define the copy-control members for the TestStr class. First, we’ll make the class act like a value; then we’ll reimplement the class making it behave like a pointer.

Classes That Act Like Values

To provide value like behavior, each object has to have its own copy of the resource that the class manages. That means each TestStr object must have its own copy of the string to which s points. To implement value like behavior TestStr needs:

  1. A copy constructor that copies the string, not just the pointer
  2. A destructor to free the string
  3. A copy-assignment operator to free the object’s existing string and copy the string from its right-hand operand
#pragma once
#include<string>
class TestStr {
public:
    // constructor
    TestStr(const std::string& os = "") : s(new std::string(os)) {}

    // copy constructor
    TestStr(const TestStr& org) :
        s(new std::string(*(org.s))) {
        // empty
    }

    // copy-assignment operator
    TestStr& operator=(const TestStr& org);

    // destructor
    ~TestStr() {
        delete s;
    }

private:
    std::string* s;
};

TestStr& TestStr::operator=(const TestStr& org) {
    auto newp = new std::string(*org.s);
    delete s;
    s = newp;
    return *this;
}

In operator= we write auto newp = new std::string(*org.s); and the s = newp, could we straightforward use s = new std::string(*org.s);, the answer is certainly not. To illustrate the importance of guarding against self-assignment, consider what would happen if run this code:

TestStr s;
s = s;

If there are same object, first free string, and then create a string by a string that has been free.

Defining Classes That Act Like Pointers

For our TestStr 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 class will still need its own destructor to free the memory allocated by the constructor that takes a string. In this case, though, the destructor cannot unilaterally free its associated string. It can do so only when the last TestStr 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. However, sometimes we want to manage a resource directly. In such cases, it can be useful to use a reference count.

Reference counting works as follows:

  1. In addition to initializing the object, each constructor (other than the copy constructor) creates a counter. This counter will keep track of how many objects share state with the object we are creating. When we create an object, there is only one such object, so we initialize the counter to 1.
  2. The copy constructor does not allocate a new counter; instead, it copies the data members of its given object, including the counter. The copy constructor increments this shared counter, indicating that there is another user of that object’s state.
  3. The destructor decrements the counter, indicating that there is one less user of the shared state. If the count goes to zero, the destructor deletes that state.
  4. The copy-assignment operator increments the right-hand operand’s counter and decrements the counter of the left-hand operand.

The only wrinkle is deciding where to put the reference count. The counter cannot be a direct member of a TestStr object. To see why, consider what happens in the following example:

TestStr p;
TestStr q(p);
TestStr r(p);

If the reference count is stored in each object, how can we update it correctly when r is created? We could increment the count in p and copy that count into r, but how would we update the counter in q?

One way to solve this problem is to store the counter in dynamic memory. When we create an object, we’ll also allocate a new counter. When we copy or assign an object, we’ll copy the pointer to the counter. That way the copy and the original will point to the same counter.

#pragma once
#include<string>

class TestStr {
public:
    // constructor
    TestStr(const std::string& os = "")
        : s(new std::string(os)), counter(new std::size_t(1)) {
        //empty
    }

    // copy constructor
    TestStr(const TestStr& org) :
        s(org.s), counter(org.counter) {
        // counter++
        (*counter)++;
    }

    // copy-assignment operator
    TestStr& operator=(const TestStr& org);

    // destructor
    ~TestStr() {
        if (--(*counter) == 0) {
            delete s;
        }
    }

private:
    std::string* s;
    std::size_t* counter;
};

TestStr& TestStr::operator=(const TestStr& org) {
    // increment the use count of the right-hand operand
    (*org.counter)++;
    // if no other users
    if (--(*counter) == 0) {
        delete s;
        delete counter;
    }
    // copy data from org into this object
    s = org.s;
    counter = org.counter;
    return *this;
}

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.

#include<iostream>

int main() {
    int a = 1, b = 2;
    std::swap(a, b);
    std::cout << a << " " << b << std::endl; // 2 1
    return 0;
}

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. Although, as usual, we don’t know how swap is implemented, conceptually it’s easy to see that swapping two objects involves a copy and two assignments. For example, code to swap two objects of our value-like TestStr class might look something like:

TestStr temp = v1; // make a temporary copy of the value of v1
v1 = v2;           // assign the value of v2 to v1
v2 = temp;         // assign the saved value of v1 to v2

In this swap function, we create a temporary copy of the value of v1. Copying a value-like TestStr allocates a new string and copies the string to which the TestStr points. In principle, none of this memory allocation is necessary, we can just do this:

string *temp = v1.ps; // make a temporary copy of the pointer in v1.ps
v1.ps = v2.ps; // assign the pointer in v2.ps to v1.ps
v2.ps = temp; // assign the saved pointer in v1.ps to v2.ps

Writing Our Own swap Function

We can override the default behavior of swap by defining a version of swap that operates on our class. The typical implementation of swap is:

// write in TestStr.h file
// note in the case wo rename TestStr to TestStr_swap
class TestStr {
public:
    friend void swap(TestStr_swap&, TestStr_swap&);
    // same as before
};

// write in TestStr.cpp file
void swap(TestStr_swap& p1, TestStr_swap& p2) {
    std::swap(p1.s, p2.s);
}

NOTICE: If you use vs2019 create a class, don’t write methods in *.h file, plz write in the corresponding *.cpp file.

We start by declaring swap as a friend to give it access to TestStr (private) data members. Then the body of swap calls swap on each of the data members of the given object.

Using swap in Assignment Operators

Classes that define swap often use swap to define their assignment operator. These operators use a technique known as copy and swap. This technique swaps the left-hand operand with a copy of the right-hand operand:

// note this version pass value rather than reference
TestStr_swap& TestStr_swap::operator=(TestStr_swap org) {
    swap(*this, org);
    return *this;
}

In the body of the assignment operator, we call swap, which swaps the data members of org with those in *this. Because we pass org by value the org will automatically call its destroy function when this function end.

A Copy-Control Example

Although copy control is most often needed for classes that allocate resources, resource management is not the only reason why a class might need to define these members. Some classes have bookkeeping or other actions that the copy-control members must perform.

As an example of a class that needs copy control in order to do some bookkeeping, we’ll sketch out two classes that might be used in a mail-handling application. These classes, Message and Folder, represent, respectively, email (or other kinds of) messages, and directories in which a message might appear. Each Message can appear in multiple Folders. However, there will be only one copy of the contents of any given Message. That way, if the contents of a Message are changed, those changes will appear when we view that Message from any of its Folders.

To keep track of which Messages are in which Folders, 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.

Our Message class will provide save and remove operations to add or remove a Message from a specified Folder. To create a new Message, we will specify the contents of the message but no Folder. To put a Message in a particular Folder, we must call save.

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.

The Message Class

// in Message.h
#pragma once
#include<string>
#include<set>
#include"Folder.h"
class Folder;
class Message {
    friend class Folder;
public:
    friend void swap(Message&, Message&);
    // constructor
    explicit Message(std::string s = "") :contents(s) {
        //empty
    }

    // copy constructor
    Message(Message& org);

    // copy assignment
    Message& operator=(const Message& org);

    // destructor
    ~Message();

    // add/remove this Message from the specified Folder's set of messages
    void save(Folder&);
    void remove(Folder&);

    // get message
    std::string& getMsg();

private:
    std::string contents;
    std::set<Folder*> folders;
    // 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();
};


// in Message.cpp
#include "Message.h"

Message::Message(Message& org)
    :contents(org.contents), folders(org.folders) {
    // add this Message to the Folders that point to org
    add_to_Folders(org);
}

Message& Message::operator=(const Message& org) {
    remove_from_Folders();
    contents = org.contents;
    folders = org.folders;
    add_to_Folders(*this);
    return *this;
}

Message::~Message() {
    // remove message pointer in folder
    remove_from_Folders();
}

void Message::save(Folder& f) {
    // add the given Folder to our list of Folders
    folders.insert(&f);
    // add this Message to f's set of Messages
    f.addMsg(this);
}

void Message::remove(Folder& f) {
    // remove the given Folder from our list of Folders
    folders.erase(&f);
    // remove this Message from f's set of Messages
    f.remMsg(this);
}

std::string& Message::getMsg() {
    return this->contents;
}

void Message::add_to_Folders(const Message& org) {
    for (auto& f : org.folders) {
        f->addMsg(this);
    }
}

void Message::remove_from_Folders() {
    for (auto& f : folders) {
        f->remMsg(this);
    }
}

void swap(Message& m1, Message& m2) {
    m1.remove_from_Folders();
    m2.remove_from_Folders();

    std::swap(m1.contents, m2.contents);
    std::swap(m1.folders, m2.folders);

    m1.add_to_Folders(m1);
    m2.add_to_Folders(m2);
}

The Folder class

// in  Folder.h
#pragma once
#include<set>
#include"Message.h"
#include<iostream>
class Message;
class Folder {
    friend class Message;
    friend void listall(Folder& f);
public:
    Folder() = default;
    ~Folder();

private:
    std::set<Message*> messages;

    // add and remove message to folder
    void addMsg(Message*);
    void remMsg(Message*);
};


// in Folder.cpp
#include "Folder.h"

Folder::~Folder() {

}

void Folder::addMsg(Message* m) {
    messages.insert(m);
}

void Folder::remMsg(Message* m) {
    messages.erase(m);
}

void listall(Folder& f) {
    for (auto& m : f.messages) {
        std::cout << m->getMsg() << " ";
    }
    std::cout << std::endl;
}

Classes That Manage Dynamic Memory

Some classes need to allocate a varying amount of storage at run time. Such classes often can (and if they can, generally should) use a library container to hold their data. For example, our StrBlob class uses a vector to manage the underlying storage for its elements.

However, this strategy does not work for every class; some classes need to do their own allocation. Such classes generally must define their own copy-control members to manage the memory they allocate.

As an example, we’ll implement a simplification of the library vector class. Among the simplifications we’ll make is that our class will not be a template. Instead, our class will hold strings. Thus, we’ll call our class StrVec.

Class StrVec design

Recall that the vector class stores its elements in contiguous storage. To obtain acceptable performance, vector pre-allocates enough storage to hold more elements than are needed. Each vector member that adds elements checks whether there is space available for another element. If so, the member constructs an object in the next available spot. 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.

Each StrVec will have three pointers into the space it uses for its elements:

  1. elements, which points to the first element in the allocated memory
  2. first_free, which points just after the last actual element
  3. cap, which points just past the end of the allocated memory

In addition to these pointers, StrVec will have a member named alloc that is an allocator<string>. The alloc member will allocate the memory used by a StrVec. Our class will also have four utility functions:

  1. alloc_n_copy will allocate space and copy a given range of elements.
  2. free will destroy the constructed elements and deallocate the space.
  3. chk_n_alloc will ensure that there is room to add at least one more element to the StrVec.
  4. reallocate will reallocate the StrVec when it runs out of space.

Key: the reallocate implement

Before we write the reallocate member, we should think a bit about what it must do. This 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 the data in these strings is unnecessary. Our StrVec performance will be much better if we can avoid the overhead of allocating and deallocating the strings themselves each time we reallocate.

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”. For string, we can imagine that
each string has a pointer to an array of char. Presumably the string move constructor copies the pointer rather than allocating space for and copying the characters themselves. The second facility we’ll use is a library function named move, which is defined in the utility header.

Class definition and check

// in StrVec.h file
#pragma once
#include<memory>
#include<string>
class StrVec {
public:
    StrVec() :elements(nullptr), first_free(nullptr), cap(nullptr) {
        //empty
    }
    StrVec(const StrVec&);
    StrVec& operator=(const StrVec&);
    ~StrVec();

    void push_back(std::string&);
    size_t size();
    size_t capacity();
    std::string* begin() const;
    std::string* end() const;

    std::string& at(size_t idx);

private:
    // private methods
    void free();
    void chk_n_alloc();
    void reallocate();
    std::pair< std::string*, std::string*> alloc_n_copy(std::string*, std::string*);

    // private attributes
    std::allocator<std::string> alloc;
    std::string* elements;
    std::string* first_free;
    std::string* cap;
};
// in StrVec.cpp file
#include "StrVec.h"

StrVec::StrVec(const StrVec& v) {
    auto newdata = alloc_n_copy(v.begin(), v.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec& StrVec::operator=(const StrVec& v) {
    auto newdata = alloc_n_copy(v.begin(), v.end());
    free();
    elements = newdata.first;
    first_free = cap = newdata.second;
    return *this;
}

StrVec::~StrVec() {
    free();
}

void StrVec::push_back(std::string& s) {
    chk_n_alloc();
    alloc.construct(first_free++, s);
}

size_t StrVec::size() {
    return first_free - elements;
}

size_t StrVec::capacity() {
    return cap - elements;
}

std::string* StrVec::begin() const {
    return elements;
}

std::string* StrVec::end() const {
    return first_free;
}

std::string& StrVec::at(size_t idx) {
    return *(elements + idx);
}

void StrVec::free() {
    if (elements) {
        alloc.deallocate(elements, cap - elements);
    }
}

void StrVec::chk_n_alloc() {
    if (size() == capacity()) {
        reallocate();
    }
}

void StrVec::reallocate() {
    auto newcap = size() ? 2 * size() : 1;
    auto newdata = alloc.allocate(newcap);
    auto dest = newdata, org = elements;
    for (auto i = 0; i != size(); i++) {
        alloc.construct(dest++, std::move(*org++));
    }
    free();
    elements = newdata;
    cap = elements + newcap;
    first_free = dest;
}

std::pair<std::string*, std::string*> StrVec::alloc_n_copy(std::string* b, std::string* e) {
    auto data = alloc.allocate(e - b);
    return { data, std::uninitialized_copy(b,e,data) };
}
#include<iostream>
#include"StrVec.h"
#include<string>

void check(StrVec& m) {
    for (size_t i = 0; i < m.size(); i++) {
        std::cout << m.at(i) << std::endl;
    }
    std::cout << "size: " << m.size() << std::endl;
    std::cout << "cap: " << m.capacity() << std::endl;
}

int main() {
    StrVec m1;
    for (size_t i = 0; i < 5; i++) {
        std::string s = std::to_string(i);
        m1.push_back(s);
    }
    StrVec m2;
    m2 = m1;
    StrVec m3(m1);

    check(m1);
    check(m2);
    check(m3);
    return 0;
}

Moving Objects

One of the major features in the new standard is the ability to move rather than copy an object. Copies are made in many circumstances. 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.

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.

Like any reference, an rvalue reference is just another name for an object. As we know, we cannot bind regular references—which we’ll refer to as lvalue references when we need to distinguish them from rvalue references—to expressions that require a conversion, to literals, or to expressions that return an rvalue.

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

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:

  1. The referred-to object is about to be destoryed
  2. There can be no other users of that object

Although we rarely think about it this way, a variable is an expression with one operand and no operator. Like any other expression, a variable expression has the lvalue/rvalue property. Variable expressions are lvalues. It may be surprising, but 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!

Given our previous observation that rvalues represent ephemeral objects, it should not be surprising that a variable is an lvalue. After all, a variable persists until it goes out of scope.

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.

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.

int &&rr3 = std::move(rr1); // ok

Move Constructor and Move Assignment

Like the string class (and other library classes), our own classes can benefit from being able to be moved as well as copied. To enable move operations for our own types, we define a move constructor and a move-assignment operator. These members are similar to the corresponding copy operations, but they “steal” resources from their given object rather than copy them.

In addition to 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. In particular, once its resources are moved, the original object must no longer point to those moved resources—responsibility for those resources has been assumed by the newly created object.

As an example, we’ll define the StrVec move constructor to move rather than copy the elements from one StrVec to another:

StrVec::StrVec(StrVec&& v)
    :elements(v.elements), first_free(v.first_free), cap(v.cap) {
    v.elements = v.first_free = v.cap = nullptr;
}

Unlike the copy constructor, the move constructor does not allocate any new memory; it takes over the memory in the given StrVec. Having taken over the memory from its argument, the constructor body sets the pointers in the given object to nullptr.

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. As we’ll see, 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. One way inform the library is to specify noexcept on our constructor.

Understanding why noexcept is needed can help deepen our understanding of how the library interacts with objects of the types we write. We need to indicate that a move operation doesn’t throw because of two interrelated facts:

  1. First, although move operations usually don’t throw exceptions, they are permitted to do so.
  2. Second, the library containers provide guarantees as to what they do if an exception happens. As one example, vector guarantees that if an exception happens when we call push_back, the vector itself will be left unchanged.

As we’ve just seen, 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. In this case, vector would be unable to meet its requirement that the vector is left unchanged.

The move-assignment operator does the same work as the destructor and the move constructor. As with the move constructor, if our move-assignment operator won’t throw any exceptions, we should make it noexcept. Like a copy-assignment operator, a move-assignment operator must guard against self-assignment:

StrVec& StrVec::operator=(StrVec&& org) noexcept {
    if (this != &org) {
        free();
        elements = org.elements;
        first_free = org.first_free;
        cap = org.cap;
        org.elements = org.first_free = org.cap = nullptr;
    }
    return *this;
}

As it does for the copy constructor and copy-assignment operator, the compiler will synthesize the move constructor and move-assignment operator. However, the conditions under which it synthesizes a move operation are quite different from those in which it synthesizes a copy operation.

The reallocate member of StrVec used a for loop to call construct to copy the elements from the old memory to the new. As an alternative to writing that loop, it would be easier if we could call uninitialized_copy to construct the newly allocated space. However, uninitialized_copy does what it says: It copies the elements. There is no analogous library function to “move” objects into unconstructed memory.

Instead, the new library defines a move iterator adaptor. 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.

void StrVec::reallocate() {
    auto newcap = size() ? 2 * size() : 1;
    auto first = alloc.allocate(newcap);
    auto last = uninitialized_copy(make_move_iterator(begin()), make_move_iterator(end()), first);
    free();
    elements = first;
    cap = elements + newcap;
    first_free = last;
}

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, and the second takes an rvalue reference to nonconst.

For example, the library containers that define push_back provide two versions: one that 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 X
void push_back(X&&); // move: binds only to modifiable rvalues of type X

Note in our test program, first use std::string s = std::to_string(i); and then m1.push_back(s);. after we write this version of push_back that parameter is a rvalue. We can abbreviation previous two statements to m1.push_back(std::to_string(i));.

void StrVec::push_back(std::string&& s) {
    chk_n_alloc();
    alloc.construct(first_free++, std::move(s));
}

life is perfact