본문으로 건너뛰기

Copy Control

Copy Control: The set of special member functions that control what happens when objects are copied, assigned, moved, and destroyed.

A class can control these operations through:

copy constructor
copy-assignment operator
move constructor
move-assignment operator
destructor

For many classes, compiler-generated versions are sufficient.

Classes that manage resources often need explicit copy-control behavior.


13.1 Copy, Assign, and Destroy

The three basic copy-control operations are:

  1. copy constructor
  2. copy-assignment operator
  3. destructor

Move operations are covered later in this chapter.


1) The Copy Constructor

Copy Constructor: A constructor whose first parameter is a reference to the class type and whose remaining parameters, if any, have default arguments.

Typical form:

class Foo
{
public:
Foo();
Foo(const Foo&);
};

The parameter is usually:

const Foo&

because copying should normally not modify the source object.


Copy Construction

Foo a;

Foo b(a);

or:

Foo b = a;

Both initialize a new object from an existing object.


Copy Initialization

Copy initialization is used in forms such as:

Foo b = a;

It is also used when an object is:

  • passed to a function by value
  • returned from a function by value
  • used to initialize certain container elements or aggregates

Example:

void process(Foo value);

Foo object;

process(object);

Passing object by value may invoke the copy constructor.


Why the Parameter Must Be a Reference

Suppose the copy constructor took its argument by value:

// Wrong idea:
Foo(Foo other);

To initialize other, the program would need to copy a Foo.

That copy would need to call the copy constructor again.

Conceptually:

copy Foo

initialize parameter Foo

copy Foo

initialize parameter Foo

...

Therefore the copy constructor parameter must be a reference.


Synthesized Copy Constructor

If a class does not define its own copy constructor, the compiler normally synthesizes one.

The synthesized copy constructor performs memberwise copy.

Example:

class Data
{
public:
std::string name;
int value = 0;
};

Conceptually, copying:

Data a;

Data b = a;

copies:

a.name -> b.name
a.value -> b.value

Each member is copied according to its own type.


Pointer Members Are Copied as Pointer Values

Consider:

class HasPtr
{
public:
std::string* pointer;
};

The synthesized copy constructor copies only the pointer value.

Conceptually:

object A ----\
-> same string
object B ----/

It does not automatically allocate a new string.

Whether this behavior is correct depends on the intended semantics of the class.


Copy Constructor and explicit

Copy constructors are usually not declared explicit.

They are used implicitly in contexts where copying is required.


2) The Copy-Assignment Operator

Copy-Assignment Operator: Replaces the state of an existing object with a copy of another object of the same type.

Typical form:

class Foo
{
public:
Foo& operator=(
const Foo&);
};

Usage:

Foo a;
Foo b;

b = a;

Unlike construction, b already exists.


Assignment vs. Initialization

Initialization:

Foo b = a;

creates a new object.

Assignment:

b = a;

changes an already existing object.

These are different operations.


Return Type

Assignment operators normally return:

Foo&

and return:

*this

Example:

Foo&
Foo::operator=(
const Foo& rhs)
{
// assign members

return *this;
}

This permits chained assignment:

a = b = c;

Synthesized Copy-Assignment Operator

If the class does not define a copy-assignment operator, the compiler normally synthesizes one.

It assigns each non-static member from the corresponding member of the right-hand operand.

Example:

class Data
{
public:
std::string name;
int value = 0;
};

For:

a = b;

the synthesized operation behaves conceptually like:

a.name = b.name;
a.value = b.value;

Self-Assignment

An assignment operator should work correctly when:

object = object;

This is especially important for resource-managing classes.

A safe assignment implementation must not destroy the source data before it has been copied.


3) The Destructor

Destructor: A special member function that performs cleanup when an object is destroyed.

Syntax:

class Foo
{
public:
~Foo();
};

A destructor:

  • has the class name preceded by ~
  • has no return type
  • takes no parameters
  • cannot be overloaded

When Destructors Run

A destructor runs when an object ceases to exist.

Examples include:

local object leaves scope

container destroys an element

dynamically allocated object is deleted

temporary object is destroyed

Example:

void function()
{
Foo object;
}

When function() ends, object is destroyed.


Destructor Body and Member Destruction

A destructor body performs class-specific cleanup.

After the destructor body finishes, non-static data members are destroyed automatically.

Example:

class Record
{
public:
~Record()
{
// custom cleanup
}

private:
std::string name;
std::vector<int> values;
};

After the destructor body:

values destroyed
name destroyed

Class-type members run their own destructors automatically.


Pointer Members Are Not Automatically Deleted

Consider:

class HasPtr
{
private:
std::string* pointer;
};

Destroying the pointer member destroys only the pointer itself.

It does not automatically perform:

delete pointer;

If the class owns that allocation, the destructor must release it.


Synthesized Destructor

If no destructor is declared, the compiler normally synthesizes one.

Its function body is effectively empty, but member destruction still occurs automatically.

For classes composed entirely of self-managing members such as:

string
vector
shared_ptr
```

the synthesized destructor is often exactly what is needed.

---

### 4) The Rule of Three/Five

The copy-control operations should usually be considered together.

A useful rule is:

**If a class needs a destructor, it almost certainly also needs a copy constructor and copy-assignment operator.**

Why?

A custom destructor often means the class owns a resource.

If the compiler simply copies the resource handle, two objects may believe they own the same resource.

---

### Example of the Problem

~~~cpp
class HasPtr
{
public:
HasPtr(
const std::string& text)
: pointer(
new std::string(text))
{
}

~HasPtr()
{
delete pointer;
}

private:
std::string* pointer;
};

If the compiler-generated copy constructor is used:

HasPtr a("hello");

HasPtr b = a;

both objects receive the same pointer.

Conceptually:

a.pointer ----\
-> dynamic string
b.pointer ----/

When both destructors execute:

delete same pointer
delete same pointer again
```

which is invalid.

---

### Rule of Three

For classes that manage resources, the traditional three operations are:

~~~text
copy constructor
copy-assignment operator
destructor

If one is needed, the others often need consideration as well.


Rule of Five

C++11 adds:

move constructor
move-assignment operator

giving five copy-control operations in total.

Resource-managing classes should consider all five together.


5) Using = default

= default: Explicitly requests the compiler-generated version of a special member function.

class Data
{
public:
Data() = default;

Data(
const Data&) = default;

Data& operator=(
const Data&) = default;

~Data() = default;
};

This makes the intended behavior explicit.


In-Class = default

When defaulted inside the class:

Data(
const Data&) = default;

the generated function is implicitly inline.


Out-of-Class = default

A special member can also be defaulted outside the class.

class Data
{
public:
Data& operator=(
const Data&);
};

Data&
Data::operator=(
const Data&) = default;

6) Preventing Copies

Some objects should not be copyable.

Examples include objects representing unique resources such as:

  • IO streams
  • exclusive handles
  • synchronization objects

C++11 uses deleted functions to express this directly.


= delete

class NoCopy
{
public:
NoCopy() = default;

NoCopy(
const NoCopy&) = delete;

NoCopy& operator=(
const NoCopy&) = delete;
};

Now:

NoCopy a;

// Error:
// NoCopy b = a;

// Error:
// b = a;

The interface clearly states that copying is forbidden.


Deleted Functions

A deleted function is declared but cannot be used.

void function(int) = delete;

For copy control, deleted functions are especially useful to prohibit copy or assignment.

Unlike = default, = delete must appear on the first declaration of the function.


Synthesized Deleted Operations

The compiler may define a synthesized special member as deleted when a member cannot perform the required operation.

For example, a synthesized copy-assignment operator may be deleted if the class contains:

const data member
reference data member
member whose assignment operator is deleted
```

The basic rule is:

~~~text
if a member cannot be copied,
assigned, or destroyed

the corresponding synthesized
class operation may be deleted

13.2 Copy Control and Resource Management

A resource-managing class must decide what copying means.

Two common semantic models are:

  1. value-like behavior
  2. pointer-like behavior

Value-Like Behavior

A value-like class gives each object its own independent resource.

Example:

object A -> string A

object B -> separate string B

Copying duplicates the resource.

Standard types such as:

string
vector
```

behave like values.

---

### Pointer-Like Behavior

A pointer-like class allows copies to share the same underlying resource.

Example:

~~~text
object A ----\
-> shared resource
object B ----/

shared_ptr and the earlier StrBlob example have pointer-like semantics.


1) Classes That Act Like Values

Consider:

class HasPtr
{
public:
HasPtr(
const std::string& text =
std::string())
: pointer(
new std::string(text)),
value(0)
{
}

private:
std::string* pointer;
int value;
};

To behave like a value, each HasPtr needs its own copy of the string.


Value-Like Copy Constructor

HasPtr::HasPtr(
const HasPtr& other)
: pointer(
new std::string(
*other.pointer
)),
value(other.value)
{
}

Conceptually:

source.pointer -> source string

copy.pointer -> separate copied string

The pointer value itself is not copied as the owned resource.

The pointed-to string is copied.


Value-Like Destructor

HasPtr::~HasPtr()
{
delete pointer;
}

Each object owns exactly one allocation.

Therefore each object releases its own allocation.


Value-Like Copy Assignment

Assignment must:

  1. copy the right-hand resource
  2. release the old left-hand resource
  3. install the copied resource
  4. copy ordinary members

A safe implementation is:

HasPtr&
HasPtr::operator=(
const HasPtr& rhs)
{
auto new_pointer =
new std::string(
*rhs.pointer
);

delete pointer;

pointer = new_pointer;
value = rhs.value;

return *this;
}

Why Copy Before Delete?

Suppose:

object = object;

If the assignment deleted pointer first, then rhs.pointer would also refer to deleted memory because rhs is the same object.

By allocating the new copy first:

copy source data

delete old data

install new data
```

self-assignment remains safe.

It also provides better exception behavior: if allocation throws, the original object is unchanged.

---

### 2) Classes That Act Like Pointers

A pointer-like class shares its resource among copies.

The easiest implementation is generally:

~~~cpp
std::shared_ptr<T>

because it already implements reference counting.

However, manual reference counting illustrates how shared ownership works.


Reference Counting

A reference count records how many objects share a resource.

Conceptually:

object A ----\
object B -----+--> shared resource
object C ----/ |
count = 3

Reference-Count Rules

The pattern is:

  1. new resource starts with count 1
  2. copy constructor increments the count
  3. destructor decrements the count
  4. last owner deletes the resource and count
  5. assignment increments the new count before releasing the old resource

Pointer-Like HasPtr

class HasPtr
{
public:
HasPtr(
const std::string& text =
std::string())
: pointer(
new std::string(text)),
value(0),
use(
new std::size_t(1))
{
}

HasPtr(
const HasPtr& other)
: pointer(other.pointer),
value(other.value),
use(other.use)
{
++*use;
}

HasPtr& operator=(
const HasPtr&);

~HasPtr();

private:
std::string* pointer;
int value;
std::size_t* use;
};

Pointer-Like Destructor

HasPtr::~HasPtr()
{
if (--*use == 0)
{
delete pointer;
delete use;
}
}

Only the last owner deletes the resource.


Pointer-Like Copy Assignment

HasPtr&
HasPtr::operator=(
const HasPtr& rhs)
{
++*rhs.use;

if (--*use == 0)
{
delete pointer;
delete use;
}

pointer = rhs.pointer;
value = rhs.value;
use = rhs.use;

return *this;
}

Why Increment First?

For self-assignment:

object = object;

the right-hand and left-hand counters are the same.

Incrementing first ensures the resource cannot be deleted before the assignment completes.

Conceptually:

increment rhs ownership

release lhs ownership

copy shared pointers
```

---

## 13.3 Swap

Resource-managing classes often define their own `swap`.

**swap:** Exchanges the states of two objects.

Generic algorithms frequently use swapping when reordering elements.

---

### Why a Custom `swap` Helps

A naive value-like swap may involve:

~~~text
copy object A
assign B to A
assign temporary to B
```

For a class that owns dynamically allocated memory, this can perform unnecessary allocations and copies.

A custom swap can exchange the resource handles instead.

---

### `HasPtr` Swap

~~~cpp
class HasPtr
{
friend void swap(
HasPtr&,
HasPtr&);

// ...
};

Definition:

void swap(
HasPtr& lhs,
HasPtr& rhs)
{
using std::swap;

swap(
lhs.pointer,
rhs.pointer
);

swap(
lhs.value,
rhs.value
);
}

Only pointer and integer values are exchanged.

The dynamically allocated strings themselves are not copied.


Why using std::swap

Inside a class-specific swap, write:

using std::swap;

swap(member1, other.member1);
swap(member2, other.member2);

rather than directly forcing:

std::swap(...)

This allows a class-specific swap for member types to participate when one exists.


Copy-and-Swap Assignment

A value-like class can implement assignment by taking the right-hand operand by value.

HasPtr&
HasPtr::operator=(
HasPtr rhs)
{
swap(*this, rhs);

return *this;
}

The parameter:

HasPtr rhs

is already a copy.

Then swap() exchanges the copied state with the current object.

When rhs is destroyed, it destroys the old state of *this.


Copy-and-Swap Flow

copy rhs into parameter

swap parameter with *this

*this owns new state

parameter owns old state

parameter destructor releases old state

This naturally handles self-assignment.


13.4 A Copy-Control Example

Copy control is not needed only for dynamic memory.

A class may need custom copy control because copying or destruction requires bookkeeping.

The chapter uses two cooperating classes:

Message
Folder

A Message may appear in several Folders.

Each side tracks the relationship.


Relationship Structure

Conceptually:

Message
|
+--> Folder A
|
+--> Folder B

Folder A
|
+--> Message

Folder B
|
+--> Message

If either side changes, the other side must also be updated.


Simplified Message

class Folder;

class Message
{
friend class Folder;

public:
explicit Message(
const std::string& text =
std::string())
: contents(text)
{
}

Message(
const Message&);

Message& operator=(
const Message&);

~Message();

void save(Folder&);
void remove(Folder&);

private:
std::string contents;
std::set<Folder*> folders;

void add_to_Folders(
const Message&);

void remove_from_Folders();
};

save()

When a message is saved in a folder:

  1. the message records the folder
  2. the folder records the message

Conceptually:

void Message::save(
Folder& folder)
{
folders.insert(&folder);

folder.addMsg(this);
}

remove()

Likewise removal updates both sides.

void Message::remove(
Folder& folder)
{
folders.erase(&folder);

folder.remMsg(this);
}

Copy Constructor Bookkeeping

A copied message should appear in the same folders as the original.

Helper:

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

Copy constructor:

Message::Message(
const Message& message)
: contents(
message.contents),
folders(
message.folders)
{
add_to_Folders(
message
);
}

The new message gets the same folder pointers.

Each folder is then updated to include the new message.


Destructor Bookkeeping

When a message is destroyed, each folder must stop referring to it.

Helper:

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

folders.clear();
}

Destructor:

Message::~Message()
{
remove_from_Folders();
}

Copy Assignment Bookkeeping

Assignment must:

  1. remove the left-hand message from its current folders
  2. copy contents and folder relationships from the right-hand message
  3. add the left-hand message to the new folders
Message&
Message::operator=(
const Message& rhs)
{
remove_from_Folders();

contents =
rhs.contents;

folders =
rhs.folders;

add_to_Folders(rhs);

return *this;
}

The order matters.


Private Utility Functions

Copy assignment often combines work also needed by:

copy constructor
destructor
```

Common bookkeeping should be placed in private helper functions rather than duplicated.

---

## 13.5 Classes That Manage Dynamic Memory

The chapter next develops a simplified `vector<string>`-like class called `StrVec`.

Its purpose is to show how a class can directly manage:

- allocated storage
- object construction
- object destruction
- reallocation
- copy control

---

### `StrVec` Storage Model

`StrVec` uses three pointers.

~~~text
elements
|
v
[constructed][constructed][unused][unused]
^ ^
| |
first_free cap

Meanings:

elements
first constructed element

first_free
one past last constructed element

cap
one past allocated storage

Simplified Class Definition

class StrVec
{
public:
StrVec()
: elements(nullptr),
first_free(nullptr),
cap(nullptr)
{
}

StrVec(
const StrVec&);

StrVec& operator=(
const StrVec&);

~StrVec();

void push_back(
const std::string&);

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

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

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

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

private:
static std::allocator<
std::string
> alloc;

void chk_n_alloc();

std::pair<
std::string*,
std::string*
>
alloc_n_copy(
const std::string*,
const std::string*);

void free();
void reallocate();

std::string* elements;
std::string* first_free;
std::string* cap;
};

push_back()

Before constructing a new element, ensure there is available storage.

void StrVec::push_back(
const std::string& text)
{
chk_n_alloc();

alloc.construct(
first_free++,
text
);
}

Checking Capacity

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

Reallocation occurs only when the allocated block is full.


alloc_n_copy()

Copy construction and assignment both need to allocate storage and copy a range.

std::pair<
std::string*,
std::string*
>
StrVec::alloc_n_copy(
const std::string* begin,
const std::string* end)
{
auto data =
alloc.allocate(
end - begin
);

return {
data,
std::uninitialized_copy(
begin,
end,
data
)
};
}

The result contains:

pointer to beginning
pointer one past copied elements

Copy Constructor

StrVec::StrVec(
const StrVec& other)
{
auto data =
alloc_n_copy(
other.begin(),
other.end()
);

elements =
data.first;

first_free =
cap =
data.second;
}

The copied object receives its own independently allocated elements.

This is value-like behavior.


free()

void StrVec::free()
{
if (elements)
{
for (auto pointer =
first_free;
pointer != elements;)
{
alloc.destroy(
--pointer
);
}

alloc.deallocate(
elements,
cap - elements
);
}
}

Objects are destroyed before the raw storage is deallocated.


Destructor

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

Copy Assignment

The assignment should allocate the new data before releasing the old data.

StrVec&
StrVec::operator=(
const StrVec& rhs)
{
auto data =
alloc_n_copy(
rhs.begin(),
rhs.end()
);

free();

elements =
data.first;

first_free =
cap =
data.second;

return *this;
}

This handles self-assignment safely.


Reallocation

When capacity is exhausted, StrVec must:

  1. allocate a larger block
  2. construct elements in the new storage
  3. destroy old elements
  4. release old storage
  5. update internal pointers

Typical growth:

new capacity =
old size * 2

or 1 when empty

Basic Reallocation

void StrVec::reallocate()
{
auto new_capacity =
size()
? 2 * size()
: 1;

auto new_data =
alloc.allocate(
new_capacity
);

auto destination =
new_data;

auto source =
elements;

for (std::size_t i = 0;
i != size();
++i)
{
alloc.construct(
destination++,
*source++
);
}

free();

elements =
new_data;

first_free =
destination;

cap =
elements
+ new_capacity;
}

Later, move operations can make this transfer more efficient.


13.6 Moving Objects

Copying can be expensive when an object owns a large resource.

Sometimes the source object is temporary and will be destroyed immediately.

Instead of copying its resource, the program can transfer ownership.

This is the purpose of move semantics.


1) Rvalue References

Rvalue Reference: A reference declared with && that normally binds to an rvalue.

int&& reference = 42;

Ordinary lvalue references use:

int&

Lvalues and Rvalues

A simplified distinction:

Lvalue: An expression representing an object with a persistent identity.

std::string text = "hello";

text is an lvalue.

Rvalue: A temporary or disposable value.

std::string("hello")

is an rvalue.


Binding Rules

An ordinary non-const lvalue reference:

std::string& ref = text;

binds to an lvalue.

An rvalue reference:

std::string&& ref =
std::string("hello");

binds to an rvalue.

A reference to const can bind to either:

const std::string& ref =
std::string("hello");

Rvalue References Identify Movable Objects

An rvalue often represents an object whose current value will not be needed later.

Therefore a function receiving:

T&&

can often safely transfer resources from that object.


Variables Are Lvalues

A subtle rule:

Even if a variable's type is an rvalue reference, the variable expression itself is an lvalue.

std::string&& ref =
std::string("hello");

The expression:

ref

is an lvalue because it has a name and persistent identity.


std::move()

std::move() converts an lvalue expression into an rvalue expression suitable for move operations.

Required header:

#include <utility>

Example:

std::string source =
"hello";

std::string target =
std::move(source);

std::move() itself does not move data.

It changes the value category so that move-enabled overloads may be selected.


After std::move()

After moving from an object:

std::string target =
std::move(source);

source still exists.

It must remain:

valid
destructible
```

but its exact value should generally not be assumed.

Safe operations include:

~~~cpp
source.clear();

source = "new value";

Do not rely on its old contents.


Use std::move() Carefully

Moving is most appropriate when the source object's current value is no longer needed.

Casual use can make program state harder to understand.

Especially in ordinary application code, move only when ownership transfer is clearly intended.


2) Move Constructor and Move Assignment

Move Constructor: Initializes a new object by transferring resources from an rvalue object.

Typical form:

ClassName(
ClassName&&) noexcept;

Move-Assignment Operator: Replaces an existing object's state by transferring resources from an rvalue.

Typical form:

ClassName&
operator=(
ClassName&&) noexcept;

StrVec Move Constructor

StrVec::StrVec(
StrVec&& source) noexcept
: elements(
source.elements),
first_free(
source.first_free),
cap(
source.cap)
{
source.elements =
nullptr;

source.first_free =
nullptr;

source.cap =
nullptr;
}

No new storage is allocated.

The three pointers are transferred.


Move Constructor Flow

Before:

source
|
v
allocated strings

After:

destination
|
v
allocated strings

source
|
v
nullptr

The destination becomes responsible for the resource.

The source is left safe to destroy.


Moved-From Objects

After a move, the source object must remain valid and destructible.

However, unless the class specifies otherwise, its exact value is unspecified.

The key rule is:

valid state
but unspecified value

noexcept

Move operations that cannot throw should normally be declared:

noexcept

Example:

StrVec(
StrVec&&) noexcept;

If the definition is outside the class, noexcept must also appear there.

StrVec::StrVec(
StrVec&& source) noexcept
: ...
{
}

Why noexcept Matters

Containers such as vector may need to relocate elements.

If moving an element might throw, the container may choose copying instead to preserve exception guarantees.

A nonthrowing move operation lets the library move elements more aggressively.


Move Assignment

A move-assignment operator must release the old resource and take ownership of the new one.

StrVec&
StrVec::operator=(
StrVec&& rhs) noexcept
{
if (this != &rhs)
{
free();

elements =
rhs.elements;

first_free =
rhs.first_free;

cap =
rhs.cap;

rhs.elements =
nullptr;

rhs.first_free =
nullptr;

rhs.cap =
nullptr;
}

return *this;
}

The explicit self-check is important because the operation destroys the current resource.


Synthesized Move Operations

The compiler may synthesize move operations, but the rules are more restrictive than for copy operations.

Move operations are synthesized only when appropriate conditions are met.

In particular, defining your own:

copy constructor
copy-assignment operator
destructor
```

prevents automatic synthesis of move operations.

---

### Memberwise Move

When synthesized, move operations move each member according to its type.

Example:

~~~cpp
struct Data
{
int value;
std::string text;
};

The integer is transferred as a value.

The string uses its move operation.


Copy Can Substitute for Move

If a class has no move constructor but has a usable copy constructor:

T object2 =
std::move(object1);

may still compile.

The copy constructor can often bind to the rvalue through:

const T&

In that case, copying occurs instead of moving.


Move and Deleted Copy Operations

Declaring move operations affects synthesized copy operations.

A class that explicitly declares a move constructor or move-assignment operator may have its synthesized copy operations defined as deleted.

Therefore copy and move support must be designed together.


Move Iterators

Move Iterator: An iterator adaptor whose dereference operation produces an rvalue reference.

Create one with:

std::make_move_iterator(
iterator
)

Required header:

#include <iterator>

Moving During StrVec Reallocation

Instead of copying strings:

std::uninitialized_copy(
begin(),
end(),
new_data
);

use move iterators:

std::uninitialized_copy(
std::make_move_iterator(
begin()
),
std::make_move_iterator(
end()
),
new_data
);

The destination strings are then constructed from rvalue references.

This allows their move constructors to be selected.


Reallocation with Move Iterators

void StrVec::reallocate()
{
auto new_capacity =
size()
? 2 * size()
: 1;

auto first =
alloc.allocate(
new_capacity
);

auto last =
std::uninitialized_copy(
std::make_move_iterator(
begin()
),
std::make_move_iterator(
end()
),
first
);

free();

elements =
first;

first_free =
last;

cap =
elements
+ new_capacity;
}

Moving can avoid allocating and copying the internal character data of each string.


3) Rvalue References and Member Functions

Move semantics can also improve ordinary member functions.

A common pair of overloads is:

void push_back(
const T&);

void push_back(
T&&);

The first copies.

The second moves.


Copying push_back

void StrVec::push_back(
const std::string& text)
{
chk_n_alloc();

alloc.construct(
first_free++,
text
);
}

An lvalue argument selects this overload.

std::string text =
"hello";

vec.push_back(text);

Moving push_back

void StrVec::push_back(
std::string&& text)
{
chk_n_alloc();

alloc.construct(
first_free++,
std::move(text)
);
}

An rvalue selects this overload.

vec.push_back(
"hello"
);

The temporary string can be moved into the container.


Why std::move(text) Is Needed Inside

Although the parameter type is:

std::string&&

the named parameter:

text

is an lvalue expression.

Therefore:

std::move(text)

is required to pass it onward as an rvalue.


Copy/Move Overload Pattern

Functions that distinguish copying from moving typically use:

const T&
```

for copying and:

~~~cpp
T&&

for moving.

A const T&& is usually not useful for moving because moving normally modifies the source.


Reference-Qualified Member Functions

Member functions can restrict whether they may be called on lvalue or rvalue objects.

Use:

&

for lvalue objects.

Use:

&&

for rvalue objects.


Lvalue-Qualified Assignment

class Foo
{
public:
Foo& operator=(
const Foo&) &;
};

This assignment operator may be called only when the left-hand object is an lvalue.

Foo a;
Foo b;

a = b;

Valid.

An assignment to a temporary result can be rejected.


Reference Qualifier Syntax

void function() &;
void function() &&;

A reference qualifier:

  • appears after the parameter list
  • applies only to non-static member functions
  • must appear in both declaration and definition

Combining const and Reference Qualifiers

The order is:

const &

or:

const &&

Example:

Foo sorted() const &;

Overloading on Reference Qualification

class Foo
{
public:
Foo sorted() &&;

Foo sorted() const &;

private:
std::vector<int> data;
};

The rvalue overload may modify the temporary directly.

Foo
Foo::sorted() &&
{
std::sort(
data.begin(),
data.end()
);

return *this;
}

Lvalue/Const Version

Foo
Foo::sorted() const &
{
Foo result(*this);

std::sort(
result.data.begin(),
result.data.end()
);

return result;
}

For an lvalue, the function sorts a copy instead of modifying the original.


Overload Selection

If:

Foo make_foo();

Foo object;

then:

make_foo().sorted();

calls the && version because the temporary result is an rvalue.

object.sorted();

calls the const & version because object is an lvalue.


Essential Study Checklist

  1. Copy control defines what happens when objects are copied, assigned, moved, and destroyed.
  2. The five copy-control members are the copy constructor, copy assignment, move constructor, move assignment, and destructor.
  3. A copy constructor normally takes const T&.
  4. Copy initialization can invoke the copy constructor.
  5. Passing or returning an object by value may invoke copy construction.
  6. The synthesized copy constructor performs memberwise copy.
  7. Pointer members are copied as pointer values by synthesized copy operations.
  8. Copy assignment modifies an already existing object.
  9. Copy-assignment operators normally return *this by reference.
  10. Assignment operators must handle self-assignment correctly.
  11. A destructor performs cleanup when an object ceases to exist.
  12. Class-type members are destroyed automatically after the destructor body.
  13. Destroying a pointer member does not delete the pointed-to object.
  14. If a class needs a destructor, it usually needs explicit copy operations as well.
  15. Resource-managing classes should consider the Rule of Three/Five.
  16. = default requests the compiler-generated special member.
  17. = delete explicitly forbids an operation.
  18. A synthesized copy-control member may be deleted when a member cannot perform the corresponding operation.
  19. Value-like classes duplicate their owned resources when copied.
  20. Pointer-like classes allow copied objects to share a resource.
  21. A value-like copy constructor must deep-copy owned resources.
  22. Value-like assignment should copy new data before releasing old data.
  23. Reference counting allows pointer-like objects to manage shared resources manually.
  24. The last reference-counted owner destroys the shared resource.
  25. A custom swap can exchange resource handles without expensive deep copies.
  26. using std::swap; swap(a, b); allows member-specific swaps to participate.
  27. Copy-and-swap assignment naturally handles self-assignment.
  28. Classes may need copy control for bookkeeping even when they do not directly manage dynamic memory.
  29. Copying a Message must update every associated Folder.
  30. Destroying a Message must remove references to it from associated folders.
  31. StrVec demonstrates manual allocation, construction, destruction, and reallocation.
  32. allocator separates raw storage allocation from object construction.
  33. A move operation transfers resources rather than duplicating them.
  34. An rvalue reference is written as T&&.
  35. std::move() converts an expression so move-enabled overloads may be selected.
  36. A named rvalue-reference variable is itself an lvalue expression.
  37. A moved-from object remains valid and destructible, but its value is generally unspecified.
  38. Move constructors should leave the source safe to destroy.
  39. Nonthrowing move operations should be declared noexcept.
  40. noexcept helps containers safely prefer moving over copying.
  41. Move assignment must release the destination's old resource before taking the source resource.
  42. User-declared copy-control members can prevent synthesis of move operations.
  43. If no move operation exists, a copy operation may be used for an rvalue.
  44. Move iterators produce rvalue references when dereferenced.
  45. make_move_iterator() can let algorithms move elements instead of copying them.
  46. Copy/move overload pairs normally use const T& and T&&.
  47. The rvalue-reference overload may transfer resources from its argument.
  48. A named T&& parameter usually needs std::move() when passed onward for moving.
  49. Reference-qualified member functions can distinguish lvalue and rvalue objects.
  50. & restricts a member to lvalue objects, while && restricts it to rvalue objects.
  51. const appears before a reference qualifier, as in const &.
  52. Reference qualification can be used to provide different behavior for persistent objects and disposable temporaries.