C++ Move Semantics - The Complete Guide - First Edition
C++ Move Semantics - The Complete Guide - First Edition
C++ Move Semantics - The Complete Guide - First Edition
Nicolai M. Josuttis
First Edition
Josuttis: C++ Move Semantics 2020/12/19 12:33 page ii
Nicolai M. Josuttis
This book was typeset by Nicolai M. Josuttis using the LATEX document processing system.
Contents
Preface xi
An Experiment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Versions of This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xii
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
iii
Josuttis: C++ Move Semantics 2020/12/19 12:33 page iv
iv Contents
1.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Contents v
6 Moved-From States 89
6.1 Required and Guaranteed States of Moved-From Objects . . . . . . . . . . . . . . . . . . . . . 89
6.1.1 Required States of Moved-From Objects . . . . . . . . . . . . . . . . . . . . . . . . . 90
Josuttis: C++ Move Semantics 2020/12/19 12:33 page vi
vi Contents
Contents vii
viii Contents
Contents ix
Glossary 231
Index 235
Josuttis: C++ Move Semantics 2020/12/19 12:33 page x
Josuttis: C++ Move Semantics 2020/12/19 12:33 page xi
Preface
Move semantics, introduced with C++11, has become a hallmark of modern C++ programming. However, it
also complicates the language in many ways. Even after several years of support, experienced programmers
struggle with all the details of move semantics, style guides still recommend different consequences for
programming even of trivial classes, and we still discuss semantic details in the C++ standards committee.
Whenever I have taught what I have learned about C++ move semantics so far, I have said, “Somebody
has to write a book about all this,” and the usual answer was: “Yes, please do!” So, I finally did.
As always when writing a book about C++, I was surprised about the number of aspects to be taught, the
situations to be clarified, and the consequences to be described. It really was time to write a book about all
aspects of move semantics, covering all C++ versions from C++11 up to C++20. I learned a lot and I am
sure you will too.
An Experiment
This book is an experiment in two ways:
• I am writing an in-depth book covering a complex core language feature without the direct help of a core
language expert as a co-author. However, I can ask questions and I do.
• I am publishing the book myself on Leanpub and for printing on demand. That is, this book is written
step by step and I will publish new versions as soon there is a significant improvement that makes the
publication of a new version worthwhile.
The good thing is:
• You get the view of the language features from an experienced application programmer—somebody who
feels the pain a feature might cause and asks the relevant questions to be able to motivate and explain the
design and its consequences for programming in practice.
• You can benefit from my experience with move semantics while I am still writing.
• This book and all readers can benefit from your early feedback.
This means that you are also part of the experiment. So help me out: give feedback about flaws, errors,
features that are not explained well, or gaps, so that we all can benefit from these improvements.
xi
Josuttis: C++ Move Semantics 2020/12/19 12:33 page xii
xii Preface
Acknowledgments xiii
Acknowledgments
First of all, I would like to thank you, the C++ community, for making this book possible. The incredible
design of all the features of move semantics, the helpful feedback, and their curiosity are the basis for the
evolution of a successful language. In particular, thanks for all the issues you told me about and explained
and for the feedback you gave.
I would especially like to thank everyone who reviewed drafts of this book or corresponding slides and
provided valuable feedback and clarification. These reviews increased the quality of the book significantly,
again proving that good things need the input of many “wise guys.” Therefore, so far (this list is still
growing) huge thanks to Javier Estrada, Howard Hinnant, Klaus Iglberger, Daniel Krügler, Marc Mutz,
Aleksandr Solovev (alexolut), Peter Sommerlad, and Tony Van Eerd.
In addition, I would like to thank everyone in the C++ community and on the C++ standards committee.
In addition to all the work involved in adding new language and library features, these experts spent many,
many hours explaining and discussing their work with me, and they did so with patience and enthusiasm.
Special thanks go to the LaTeX community for a great text system and to Frank Mittelbach for solving
my LATEX issues (it was almost always my fault).
And finally, many thanks go to my proofreader, Tracey Duffy, who has done a tremendous job of con-
verting my “German English” into native English.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page xiv
xiv
This book teaches C++ move semantics. Starting from the basic principles, it motivates and explains all
features and corner cases of move semantics so that as a programmer, you can understand and use move
semantics correctly. The book is valuable for those who are just starting to learn about move semantics and
is essential for those who are using it already.
As usual for my books, the focus lies on the application of the new features in practice and the book will
demonstrate how features impact day-to-day programming and how you can benefit from them in projects.
This applies to both application programmers and programmers who provide generic frameworks and foun-
dation libraries.
xv
Josuttis: C++ Move Semantics 2020/12/19 12:33 page xvi
Initializations
I usually use the modern form of initialization (introduced in C++11 as uniform initialization) with curly
braces:
int i{42};
std::string s{"hello"};
This form of initialization, which is called brace initialization, has the following advantages:
• It can be used with fundamental types, class types, aggregates, enumeration types, and auto
• It can be used to initialize containers with multiple values
• It can detect narrowing errors (e.g., initialization of an int by a floating-point value)
• It cannot be confused with function declarations or calls
If the braces are empty, the default constructors of (sub)objects are called and fundamental data types are
guaranteed to be initialized with 0/false/nullptr.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page xvii
Error Terminology
I often talk about programming errors. If there is no special hint, the term error or a comment such as
... // ERROR
means a compile-time error. The corresponding code should not compile (with a conforming compiler).
If I use the term runtime error, the program might compile but not behave correctly or result in undefined
behavior (thus, it might or might not do what is expected).
Code Simplifications
I try to explain all features with helpful examples. However, to concentrate on the key aspects to be taught,
I might often skip other details that should be part of code.
• Most of the time I use an ellipsis (“...”) to signal additional code that is missing. Note that I do not use
code font here. If you see an ellipsis with code font, code must have these three dots as a language feature
(such as for “typename...”).
• In header files I usually skip the preprocessor guards. All header files should have something like the
following:
#ifndef MYFILE_HPP
#define MYFILE_HPP
...
#endif // MYFILE_HPP
So, please beware and fix the code when using these header files in your projects.
Feedback
I welcome your constructive input—both negative and positive. I have worked very hard to bring you what
I hope you will find to be an excellent book. However, at some point I had to stop writing, reviewing, and
tweaking to “release the new revision.” You may therefore find errors, inconsistencies, presentations that
could be improved, or topics that are missing altogether. Your feedback gives me a chance to fix these issues,
inform all readers about the changes through the book’s website, and improve any subsequent revisions or
editions.
The best way to reach me is by email. You will find the email address at the website for this book:
http://www.cppmove.com
If you use the ebook, you might want to ensure to have the latest version of this book available (remember
it is written and published incrementally). You should also check the book’s Web site for the currently
known errata before submitting reports. In any case, refer to the publishing date of this version when giving
feedback. The current publishing date is 2020-12-19 (you can also find it on page ii, the page directly after
the cover).
Many thanks.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 1
Part I
Basic Features of Move Semantics
This part of the book introduces the basic features of move semantics that are not specific to generic pro-
gramming (i.e., templates). They are particularly helpful for application programmers in their day-to-day
programming and therefore every C++ programmer using Modern C++ should know them.
Move semantics features for generic programming are covered in Part II.
1
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 2
Chapter 1
The Power of Move Semantics
This chapter demonstrates the basic principles and benefits of move semantics using a short code example.
std::vector<std::string> createAndInsert()
{
std::vector<std::string> coll; // create vector of strings
coll.reserve(3); // reserve memory for 3 elements
std::string s = "data"; // create string object
3
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 4
int main()
{
std::vector<std::string> v; // create empty vector of strings
...
v = createAndInsert(); // assign returned vector of strings
...
}
Let us look at the individual steps of the program (inspecting both the stack and the heap) when we compile
this program with a C++ compiler that does not support move semantics.
• First, in main(), we create the empty vector v:
std::vector<std::string> v;
which is placed on the stack as an object that has 0 as the number of elements and no memory allocated
for elements.
• Then, we call
v = createAndInsert();
where we create another empty vector coll on the stack and reserve memory for three elements on the
heap:
std::vector<std::string> coll;
coll.reserve(3);
The allocated memory is not initialized because the number of elements is still 0.
• Then, we create a string initialized with "data":
std::string s = "data";
A string is something like a vector with char elements. Essentially, we create an object on the stack
with a member for the number of characters (having the value 4) and a pointer to the memory for the
characters.1
After this statement, the program has the following state: we have three objects on the stack: v, coll,
and s. Two of them, coll and s, have allocated memory on the heap:2
1 Internally, strings also store a terminating null character to avoid allocating memory when they are asked for a C
string representation of their value with the member function c_str().
2 With the small string optimization (SSO), the string s might store its whole value on the stack provided the value is
not too long. However, for the general case, let us assume that we do not have the small string optimization or the
value of the string is long enough so that the small string optimization does not happen.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 5
• The next step is the command to insert the string into the vector coll:
coll.push_back(s);
All containers in the C++ standard library have value semantics, which means that they create copies of
the values passed to them. As a result, we get a first element in the vector, which is a full (deep) copy of
the passed value/object s:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 6
So far, we have nothing to optimize in this program. The current state is that we have two vectors, v and
coll, and two strings, s and its copy, which is the first element in coll. They should all be separate
objects with their own memory for the value, because modifying one of them should not impact any of
the other objects.
• Let us now look at the next statement, which creates a new temporary string and again inserts it into the
vector:
coll.push_back(s+s);
This statement is performed in three steps:
1. We create the temporary string s+s:
2. We insert this temporary string into the vector coll. As always, the container creates a copy of the
passed value, which means that we create a deep copy of the temporary string, including allocating
memory for the value:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 7
3. At the end of the statement, the temporary string s+s is destroyed because we no longer need it:
Here, we have the first moment where we generate code that is not performing well: we create a copy
of a temporary string and destroy the source of the copy immediately afterwards, which means that we
unnecessarily allocate and free memory that we could have just moved from the source to the copy.
• With the next statement, again we insert s into coll:
coll.push_back(s);
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 8
This is also something to improve: because the value of s is no longer needed some optimization could
use the memory of s as memory for the new element in the vector instead.
• At the end of createAndInsert() we come to the return statement:
return coll;
}
Here, the behavior of the program becomes a bit more complicated. We return by value (the return type
is not a reference), which should be a copy of the value in the return statement, coll. Creating a copy
of coll means that we have to create a deep copy of the whole vector with all of its elements. Thus, we
have to allocate heap memory for the array of elements in the vector and heap memory for the value each
string allocates to hold its value. Here, we would have to allocate memory 4 times.
However, since at the same time coll is destroyed because we leave the scope where it is declared,
the compiler is allowed to perform the named return value optimization (NRVO). This means that the
compiler can generate code so that coll is just used as the return value.
This optimization is allowed even if this would change the functional behavior of the program. If we
had a print statement in the copy constructor of a vector or string, we would see that the program no
longer has the output from the print statement. This means that this optimization changes the functional
behavior of the program. However, that is OK, because we explicitly allow this optimization in the C++
standard even if it has side effects. Nobody should expect that a copy is done here, in the same way that
nobody should expect that it is not, either. It is simply up to the compiler whether the named return value
optimization is performed.
Let us assume that we have the named return value optimization. In that case, at the end of the return
statement, coll now becomes the return value and the destructor of s is called, which frees the memory
allocated when it was declared:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 9
However, right after that, we no longer need the temporary return value and we destroy it:
Again, we create a copy of a temporary object and destroy the source of the copy immediately after-
wards, which means that we again unnecessarily allocate and free memory. This time it applies to four
allocations, one for the vector and one for each string element.
For the state of this program after the assignment in main(), we allocated memory ten times and released it
six times. The unnecessary memory allocations were caused by:
• Inserting a temporary object into the collection
• Inserting an object into the collection where we no longer need the value
• Assigning a temporary vector with all its elements
We can more or less avoid these performance penalties. In particular, instead of the last assignment, we
could do the following:
• Pass the vector as an out parameter:
createAndInsert(v); // let the function fill vector v
• Use swap():
createAndInsert().swap(v);
However, the resulting code looks uglier (unless you see some beauty in complex code) and there is not
really a workaround when inserting a temporary object.
Since C++11, we have another option: compile and run the program with support for move semantics.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 11
std::vector<std::string> createAndInsert()
{
std::vector<std::string> coll; // create vector of strings
coll.reserve(3); // reserve memory for 3 elements
std::string s = "data"; // create string object
int main()
{
std::vector<std::string> v; // create empty vector of strings
...
v = createAndInsert(); // assign returned vector of strings
...
}
There is a small modification, though: we add a std::move() call when we insert the last element into
coll. We will discuss this change when we come to this statement. Everything else is as before.
Again, let us look at the individual steps of the program by inspecting both the stack and the heap.
• First, in main(), we create the empty vector v, which is placed on the stack with 0 elements:
std::vector<std::string> v;
• Then, we call
v = createAndInsert();
where we create another empty vector coll on the stack and reserve uninitialized memory for three
elements on the heap:
std::vector<std::string> coll;
coll.reserve(3);
• Then, we create the string s initialized with "data" and insert it into coll again:
std::string s = "data";
coll.push_back(s);
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 12
So far, there is nothing to optimize and we get the same state as with C++03:
We have two vectors, v and coll, and two strings, s and its copy, which is the first element in coll.
They should all be separate objects with their own memory for the value, because modifying one of them
should not impact any of the other objects.
• This is where things change. First, let us look at the statement that creates a new temporary string and
inserts it into the vector:
coll.push_back(s+s);
Again, this statement is performed in three steps:
1. We create the temporary string s+s:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 13
2. We insert this temporary string into the vector coll. However, here something different happens now:
we steal the memory for the value from s+s and move it to the new element of coll.
This is possible because since C++11, we can implement special behavior for getting a value that is
no longer needed. The compiler can signal this fact because it knows that right after performing the
push_back() call, the temporary object s+s will be destroyed. So, we call a different implementation
of push_back() provided for the case when the caller no longer needs that value. As we can see, the
effect is an optimized implementation of copying a string where we no longer need the value: instead
of creating an individual deep copy, we copy both the size and the pointer to the memory. However,
that shallow copy is not enough; we also modify the temporary object s+s by setting the size to 0 and
assigning the nullptr as new value. Essentially, s+s is modified so that it gets the state of an empty
string. The important point is that it no longer owns its memory. And that is important because we
still have a third step in this statement.
3. At the end of the statement, the temporary string s+s is destroyed because we no longer need it.
However, because the temporary string is no longer the owner of the initial memory, the destructor
will not free this memory.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 14
Essentially, we optimize the copying so that we move the ownership of the memory for the value of s+s
to its copy in the vector.
This is all done automatically by using a compiler that can signal that an object is about to die, so that
we can use new implementations to copy a string value that steals the value from the source. It is not a
technical move; it is a semantic move implemented by technically moving the memory for the value from
the source string to its copy.
• The next statement is the statement we modified for the C++11 version. Again, we insert s into coll,
but the statement has changed by calling std::move() for the string s that we insert:
coll.push_back(std::move(s));
Without std::move(), the same would happen as with the first call of push_back(): the vector would
create a deep copy of the passed string s. However, in this call, we have marked s with std::move(),
which semantically means “I no longer need this value here.” As a consequence, we have another call of
the other implementation of push_back(), which was used when we passed the temporary object s+s.
The third element steals the value by moving the ownership of the memory for the value from s to its
copy:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 15
Note the following two very important things to understand about move semantics:
– std::move(s) only marks s to be movable in this context. It does not move anything. It only says,
“I no longer need this value here.” It allows the implementation of the call to benefit from this mark
by performing some optimization when copying the value, such as stealing the memory. Whether the
value is moved is something the caller does not know.
– However, an optimization that steals the value has to ensure that the source object is still in a valid state.
A moved-from object is neither partially nor fully destroyed. The C++ standard library formulates this
for its types as follows: after an operation called for an object marked with std::move(), the object
is in a valid but unspecified state.
That is, after calling
coll.push_back(std::move(s));
it is guaranteed that s is still a valid string. You can do whatever you want as long as it is valid for
any string where you do not know the value. It is like using a string parameter where you do not know
which value was passed.
Note that it is also not guaranteed that the string either has its old value or is empty. The value it
has is up to the implementers of the (library) function. In general, implementers can do with objects
marked with std::move() whatever they like, provided they leave the object in a valid state. There
are good reasons for this guarantee, which will be discussed later.
is about to die. That is, if the named return value optimization is not used, move semantics will be used,
which means that the return value steals the value from coll. At worst, we have to copy the members
for size, capacity, and the pointer to the memory (in total, usually 12 or 24 bytes) from the source to the
return value and assign new values to these members in the source.
Let us assume that we have the named return value optimization. In that case, at the end of the return
statement, coll now becomes the return value and the destructor of s is called, which no longer has to
free any memory because it was moved to the third element of coll:
Again, the temporary object is not (partially) destroyed. It enters into a valid state but we do not know its
value.
However, right after the assignment, the end of the statement destroys the (modified) temporary return
value:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 18
At the end we are in the same state as before using move semantics but something significant has changed:
we saved six allocations and releases of memory. All unnecessary memory allocations no longer took place:
• Allocations for inserting a temporary object into the collection
• Allocations for inserting a named object into the collection, when we use std::move() to signal that we
no longer need the value
• Allocations for assigning a temporary vector with all its elements
In the second case, the optimization was done with our help. By adding std::move(), we had to say that
we no longer needed the value of s there. All other optimizations happened because the compiler knows that
an object is about to die, meaning that it can call the optimized implementation, which uses move semantics.
This means that returning a vector of strings and assigning it to an existing vector is no longer a perfor-
mance issue. We can use a vector of strings naively like an integral type and get much better performance.
In practice, recompiling code with move semantics can improve speed by 10% to 40% (depending on how
naive the existing code was).
The second push_back() uses a new syntax introduced for move semantics. We declare the argument with
two & and without const. Such an argument is called an rvalue reference.3 “Ordinary references” that
have only one & are now called lvalue references. That is, in both calls we pass the value to be inserted by
reference. However, the difference is as follows:
• With push_back(const T&), we promise not to modify the passed value.
This function is called when the caller still needs the passed value.
• With push_back(T&&), the implementation can modify the passed argument (therefore it is not const)
to “steal” the value. The semantic meaning is still that the new element receives the value of the passed
argument but we can use an optimized implementation that moves the value into the vector.
This function is called when the caller no longer needs the passed value. The implementation has to
ensure that the passed argument is still in a valid state. However, the value may be changed. Therefore,
after calling this, the caller can still use the passed argument as long as the caller does not make any
assumption about its value.
However, a vector does not know how to copy or move an element. After making sure that the vector has
enough memory for the new element, the vector delegates the work to the type of the elements.
In this case, the elements are strings. So, let us see what happens if we copy or move the passed string.
public:
// copy constructor: create a full copy of s:
string (const string& s)
: len{s.len} { // copy number of characters
if (len > 0) { // if not empty
data = new char[len+1]; // - allocate new memory
memcpy(data, s.data, len+1); // - and copy the characters
}
}
...
};
Given that we call this copy constructor for a string that has the value "data":
3 The reason this is called an rvalue reference is discussed later in the chapter about value categories.
4 The implementation of class std::string is more complicated because for its internal memory management it uses
optimizations and allocators (helper objects that define the way to allocate memory).
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 20
std::string a = "data";
std::string b = a; // create b as a copy of a
after initializing the string a as follows:
the copy constructor above would copy the member len for the number of characters but assign new memory
for the value to the data pointer and copy all characters from the source a (passed as s) to the new string:
public:
...
// move constructor: initialize the new string from s (stealing the value):
string (string&& s)
: len{s.len}, data{s.data} { // copy number of characters and pointer to memory
s.data = nullptr; // release the memory for the source value
s.len = 0; // and adjust number of characters accordingly
}
...
};
Given the situation from the copy constructor above:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 21
However, this is not enough, because the destructor of b would free the memory. Therefore, we also modify
the source string to lose its ownership of the memory and bring it into a consistent state representing the
empty string:
The effect is that c now has the former value of b and that b is the empty string. Again, note that the
only guarantee is that b is subsequently in a valid but unspecified state. Depending on the way the move
constructor is implemented in a C++ library, it might not be empty (but it usually is because this is the easiest
and best way to improve performance here).
class MyVector {
public:
...
void push_back (const T& elem); // insert a copy of elem
... // no other push_back() declared
};
We can still pass a temporary object or an object marked with std::move():
MyVector<std::string> coll;
std::string s{"data"};
...
coll.push_back(std::move(s)); // OK, uses copy semantics
The rule is that for a temporary object or an object marked with std::move(), if available, a function
declaring the parameter as an rvalue reference is preferred. However, if no such function exists, the usual
copy semantics is used. That way, we ensure that the caller does not have to know whether an optimization
exists. The optimization might not exist because:
• The function/class was implemented before move semantics was supported or without having move se-
mantics support in mind
• There is nothing to optimize (a class with only numeric members would be an example of that)
For generic code, it is important that we can always mark an object with std::move() if we no longer need
its value. The corresponding code compiles even if there is no move semantics support.
For the same reason, you can even mark objects of a fundamental data type such as int (or a pointer)
with std::move(). The usual value semantics copying the value (the address) will still be used:
std::vector<int> coll;
int x{42};
...
coll.push_back(std::move(x)); // OK, but copies x (std::move() has no effect)
1.5 Summary 23
};
the only valid function to call for const objects is the first overload of push_back() with the const&
parameter:
std::vector<std::string> coll;
const std::string s{"data"};
...
coll.push_back(std::move(s)); // OK, calls push_back(const std::string&)
That means that a std::move() for const objects essentially has no effect.
In principle, we could provide a special overload for this case by declaring a function taking a const
rvalue reference. However, this makes no semantic sense. Again, the const lvalue reference overload
serves as a fallback to handle this case.
std::vector<std::string> coll;
...
coll.push_back(getValue()); // copies (because the return value is const)
When returning by value, do not declare the return value as a whole to be const. Use const only to declare
parts of your return type (such as the object a returned reference or pointer refers to):
const std::string getValue(); // BAD: disables move semantics for return values
const std::string& getRef(); // OK
const std::string* getPtr(); // OK
1.5 Summary
• Move semantics allows us to optimize the copying of objects, where we no longer need the value. It can be
used implicitly (for unnamed temporary objects or local return values) or explicitly (with std::move()).
• std::move() means I no longer need this value here. It marks the object as movable. An object marked
with std::move() is not (partially) destroyed (the destructor still will be called).
• By declaring a function with a non-const rvalue reference (such as std::string&&), you define an
interface where the caller semantically claims that it no longer needs the passed value. The implementer
of the function can use this information to optimize its task by “stealing” the value or do any other
modification with the passed argument. Usually, the implementer also has to ensure that the passed
argument is in a valid state after the call.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 24
• Moved-from objects of the C++ standard library are still valid objects, but you no longer know their
value.
• Copy semantics is used as a fallback for move semantics (if copy semantics is supported). If there is no
implementation taking an rvalue reference, any implementation taking an ordinary const lvalue reference
(such as const std::string&) is used. This fallback is then used even if the object is explicitly marked
with std::move().
• Calling std::move() for a const object usually has no effect.
• If you return by value (not by reference), do not declare the return value as a whole to be const.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 25
Chapter 2
Core Features of Move Semantics
After the first motivating example, this chapter discusses the basic features of move semantics.
25
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 26
std::string s{"hello"};
...
std::string&& r1 = std::move(s); // OK, rvalue reference to s
std::string&& r2{std::move(s)}; // OK, rvalue reference to s
std::string&& r3(std::move(s)); // OK, rvalue reference to s
All these references have the semantics of “we can steal/modify the object we refer to, provided the state
of the object remains a valid state.” Technically, these semantics are not checked by compilers, so we can
modify an rvalue reference as we can do with any non-const object of the type. We might also decide not
to modify the value. That is, if you have an rvalue reference to an object, the object might receive a different
value (which might or might not be the value of a default-constructed object) or it might keep its value.
As we have seen, move semantics allows us to optimize using a value of a source that no longer needs the
value. If compilers automatically detect that a value is used from an object that is at the end of its lifetime,
they will automatically switch to move semantics. This is the case when:
• We pass the value of a temporary object that will automatically be destroyed after the statement.
• We pass a non-const object marked with std::move().
2.2 std::move() 27
std::cout << s << '\n'; // OOPS, you don’t know which value is printed
foo(std::move(s)); // OOPS, you don’t know which value is passed
s = "hello again"; // OK, but rarely done
foo(std::move(s)); // OK, value of s might change
For both lines marked with “OOPS,” the call is technically OK as long as you make no assumption about
the current value of s. Printing out the value is therefore fine, although usually not very useful.
2.2 std::move()
If you have an object for which the lifetime does not end when you use it, you can mark it with std::move()
to express “I no longer need this value here.” std::move() does not move; it only sets a temporary marker
in the context where the expression is used:
void foo1(const std::string& lr); // binds to the passed object without modifying it
void foo1(std::string&& rv); // binds to the passed object and might steal/modify the value
...
std::string s{"hello"};
...
foo1(s); // calls the first foo1(), s keeps its value
foo1(std::move(s)); // calls the second foo1(), s might lose its value
Objects marked with std::move() can still be passed to a function that takes an ordinary const lvalue
reference:
void foo2(const std::string& lr); // binds to the passed object without modifying it
... // no other overload of foo2()
std::string s{"hello"};
...
foo2(s); // calls foo2(), s keeps its value
foo2(std::move(s)); // also calls foo2(), s keeps its value
Note that an object marked with std::move() cannot be passed to a non-const lvalue reference:
void foo3(std::string&); // modifies the passed argument
...
std::string s{"hello"};
...
foo3(s); // OK, calls foo3()
foo3(std::move(s)); // ERROR: no matching foo3() declared
Note that it does not make sense to mark a dying object with std::move(). In fact, this can even be
counterproductive for optimizations.
Programs using std::move() usually compile without including this header file, because in practice almost
all header files include <utility>. However, no standard header file is required to include utility.
Therefore, when using std::move(), you should explicitly include <utility> to make your program
portable.
Although you do not know the value, the string is in a consistent state. For example, s.size() will return
the number of characters so that you can iterate over all valid indexes:
foo(std::move(s)); // keeps s in a valid but unclear state
1 The guarantees for moved-from library objects were clarified with the library working group issue 2839 (see http:
//wg21.link/lwg2839).
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 31
The semantic meaning is that we give foo() write access to the passed argument to steal the value. It is
an in parameter with the additional constraint that the caller no longer needs the value.
Note that rvalue references bind to other arguments than non-const lvalue references. Therefore, we had
to introduce a new syntax and could not just implement move semantics as a different way of functions that
modify passed arguments.
2.6 Summary
• Rvalue references are declared with && and no const.
• They can be initialized by temporary objects that do not have a name or non-const objects marked with
std::move().
• Rvalue references extend the lifetime of objects returned by value.
• std::move() is a static_cast to the corresponding rvalue reference type. This allows us to pass a
named object to an rvalue reference.
• Objects marked with std::move() can also be passed to functions taking the argument by const lvalue
reference but not taking a non-const lvalue reference.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 33
2.6 Summary 33
• Objects marked with std::move() can also be passed to functions taking the argument by value. In that
case, move semantics is used to initialize the parameter, which can make call-by-value pretty cheap.
• const rvalue references are possible but implementing against them usually makes no sense.
• Moved-from objects should be in a valid but unspecified state. The C++ standard library guarantees that
for its types. You can still (re)use them providing you do not make any assumptions about their value.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 34
34
Chapter 3
Move Semantics in Classes
This chapter shows how classes can benefit from move semantics. It demonstrates how ordinary classes
automatically benefit from move semantics and how to explicitly implement move operations in classes.
basics/customer.hpp
#include <string>
#include <vector>
#include <iostream>
#include <cassert>
class Customer {
private:
std::string name; // name of the customer
std::vector<int> values; // some values of the customer
public:
Customer(const std::string& n)
: name{n} {
assert(!name.empty());
}
35
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 36
values.push_back(val);
}
int main()
{
// create a customer with some initial values:
Customer c{"Wolfgang Amadeus Mozart" };
for (int val : {0, 8, 15}) {
c.addValue(val);
}
std::cout << "c: " << c << '\n'; // print value of initialized c
However, because move semantics is provided to optimize performance and assigning a different value is
not necessarily a way to improve performance, it is quite typical that implementations make both the string
and the vector empty.
In any case, we can see that move semantics is automatically enabled for the class Customer. For the
same reason, it is now guaranteed that the following code is cheap:
Customer createCustomer()
{
Customer c{ ... };
...
return c; // uses move semantics if not optimized away
}
std::vector<Customer> customers;
...
customers.push_back(createCustomer()); // uses move semantics
See basics/customer2.cpp for the complete example.
The important message is that, since C++11, classes automatically benefit from move semantics if they
use members that benefit from it. These classes have:
• A move constructor that moves the members if we create a new object from a source where we no longer
need the value:
Customer c1{ ... }
...
Customer c2{std::move(c1)}; // move members of c1 to members of c2
• A move assignment operator that move assigns the members if we assign the value from a source where
we no longer need the value.
Customer c1{ ... }, c2{ ... };
...
c2 = std::move(c1); // move assign members of c1 to members of c2
Note that such a class can benefit even further from move semantics by explicitly implementing the following
improvements:
• Use move semantics when initializing members
• Use move semantics to make getters both safe and fast
• Copy constructor
• Copy assignment operator
• Another move operation
• Destructor
Note that I wrote “user-declared.” Any form of an explicit declaration of a copy constructor, copy assign-
ment operator, or destructor disables move semantics. For example, if we implement a destructor that does
nothing, we have disabled move semantics:
class Customer {
...
~Customer() { // automatic move semantics is disabled
}
};
Even the following declaration is enough to disable move semantics:
class Customer {
...
~Customer() = default; // automatic move semantics is disabled
};
A destructor explicitly requested to have its default behavior is user-declared and therefore disables move
semantics. As usual, copy semantics will be used as a fallback in that case.
Therefore: do not implement or declare a destructor if there is not specific need (a rule a surprising
number of programmers do not follow).
This also means that by default, a polymorphic base class has disabled move semantics:
class Base {
...
virtual ~Base() { // automatic move semantics is disabled
}
};
Note that this means that move semantics is disabled only for members that are declared inside this base
class. For members of derived classes, move semantics is still automatically generated (if the derived class
does not explicitly declare a special member function). Move Semantics in Class Hierarchies discusses this
in detail.
The problem that can occur is that moved-from objects might no longer be valid: invariants might be broken
or the destructor of the object might even fail. For example, objects of the Customer class in this chapter
might suddenly have an empty name even though we have assertions to avoid that. The chapter about
moved-from states will discuss this in detail.
class Customer {
private:
std::string name; // name of the customer
std::vector<int> values; // some values of the customer
public:
Customer(const std::string& n)
: name{n} {
assert(!name.empty());
}
return strm;
}
public:
...
// copy constructor (copy all members):
Customer(const Customer& cust)
: name{cust.name}, values{cust.values} {
std::cout << "COPY " << cust.name << '\n';
}
...
};
The automatically generated copy constructor does just copy all members. In our implementation, we only
add a statement printing that a specific customer is copied.
Therefore, the fact that move semantics is not passed through is a feature, not a bug. If we were to pass
move semantics through, we would not be able to use an object that was passed with move semantics twice.
For example:
void insertTwice(std::vector<std::string>& coll, std::string&& str)
{
coll.push_back(str); // copy str into coll
coll.push_back(std::move(str)); // move str into coll
}
If all uses of str implicitly had move semantics, the value of str would be moved away with the first
push_back() call.
The important lesson to learn here is that a parameter being declared as an rvalue reference restricts what
we can pass to this function but behaves just like any other non-const object of this type. We again have
to specify when and where we no longer need the value. See the formal discussion When Rvalues become
Lvalues for more details.
One additional note: while the print statement of the copy constructor prints the name of the passed
customer:
Customer(const Customer& cust)
: name{cust.name}, values{cust.values} {
std::cout << "COPY " << cust.name << '\n'; // cust.name still there
}
the move constructor cannot use cust.name because in the initialization of the constructor the value might
have been moved away. We have to use the member of the new objects instead:
Customer(Customer&& cust)
: name{std::move(cust.name)}, values{std::move(cust.values)} {
std::cout << "MOVE " << name << '\n'; // have to use name (cust.name moved away)
}
Note that you should always implement the move constructor with a (conditional) noexcept specification
to improve the performance of reallocations of a vector of Customers.
values = cust.values;
return *this;
}
...
};
Like the automatically generated copy assignment operator, we simply assign all members. The only differ-
ence is the print statement at the beginning.
When implementing an assignment operator, you can (and maybe should) check for assignments of an
object to itself. However, note that the default assignment operator generated does not do that and see the
comments about this when discussing self-assignments with the move assignment operator.
You might also want to declare the assignment operator with reference qualifiers.
2 See http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-move-self.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 46
int main()
{
std::vector<Customer> coll;
for (int i=0; i<12; ++i) {
coll.push_back(Customer{"TestCustomer " + std::to_string(i-5)});
}
Initialization
The first part is the initialization of the vector with 12 customers:
std::vector<Customer> coll;
for (int i=0; i<12; ++i) {
coll.push_back(Customer{"TestCustomer " + std::to_string(i-5)});
}
This initialization might have the following output:
MOVE TestCustomer -5
MOVE TestCustomer -4
COPY TestCustomer -5
MOVE TestCustomer -3
COPY TestCustomer -5
COPY TestCustomer -4
MOVE TestCustomer -2
MOVE TestCustomer -1
COPY TestCustomer -5
COPY TestCustomer -4
COPY TestCustomer -3
COPY TestCustomer -2
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 47
MOVE TestCustomer 0
MOVE TestCustomer 1
MOVE TestCustomer 2
MOVE TestCustomer 3
COPY TestCustomer -5
COPY TestCustomer -4
COPY TestCustomer -3
COPY TestCustomer -2
COPY TestCustomer -1
COPY TestCustomer 0
COPY TestCustomer 1
COPY TestCustomer 2
MOVE TestCustomer 4
MOVE TestCustomer 5
MOVE TestCustomer 6
Each time we insert a new object, a temporary object is created and moved into the vector. Therefore, for
each element, we have a MOVE.
In addition, we have several copies marked with COPY. These copies are caused by the fact that from
time to time, a vector reallocates its internal memory (capacity) so that it is big enough for all elements.
In this case, the vector grows from having memory for 1 to having memory for 2, 4, 8, and finally 16
elements. Therefore, we have to copy first 1, then 2, then 4, and then 8 elements already in the vector.
Calling coll.reserve(20) before the loop would avoid these copies. However, you might wonder why
no move is used here. This has to do with the missing noexcept declarations, which we will discuss in the
chapter about move semantics and noexcept.
Note that the exact policy of a vector to grow its capacity is implementation-specific. Thus, the output
might differ when implementations grow differently (such as growing by 50% each time).
Sorting
Next, we sort all elements by name:
std::sort(coll.begin(), coll.end(),
[] (const Customer& c1, const Customer& c2) {
return c1.getName() < c2.getName();
});
This sorting might have the following output:
MOVE TestCustomer -4
MOVEASSIGN TestCustomer -5
MOVEASSIGN TestCustomer -4
MOVE TestCustomer -3
MOVEASSIGN TestCustomer -5
MOVEASSIGN TestCustomer -4
MOVEASSIGN TestCustomer -3
MOVE TestCustomer -2
MOVEASSIGN TestCustomer -5
MOVEASSIGN TestCustomer -4
MOVEASSIGN TestCustomer -3
MOVEASSIGN TestCustomer -2
MOVE TestCustomer -1
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 48
MOVEASSIGN TestCustomer -5
MOVEASSIGN TestCustomer -4
MOVEASSIGN TestCustomer -3
MOVEASSIGN TestCustomer -2
MOVEASSIGN TestCustomer -1
MOVE TestCustomer 0
MOVEASSIGN TestCustomer 0
MOVE TestCustomer 1
MOVEASSIGN TestCustomer 1
MOVE TestCustomer 2
MOVEASSIGN TestCustomer 2
MOVE TestCustomer 3
MOVEASSIGN TestCustomer 3
MOVE TestCustomer 4
MOVEASSIGN TestCustomer 4
MOVE TestCustomer 5
MOVEASSIGN TestCustomer 5
MOVE TestCustomer 6
MOVEASSIGN TestCustomer 6
As you can see, the whole sorting is only moving around elements; sometimes to create a new temporary
object (MOVE), sometimes to assign a value to a different location (MOVEASSIGN).
Again, the output depends on the exact implementation of sort(), which is implementation-specific.
There are a few basic rules you can see in this table:
• A default constructor is only declared automatically if no other constructor is user-declared.
• The special copy member functions and the destructor disable move support. The automatic generation
of special move member functions is disabled (unless the moving operations are also declared). However,
3 This table is adopted from Howard Hinnant with his kind permission.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 50
still a request to move an object usually works because the copy member functions are used as a fallback
(unless the special move member functions are explicitly deleted).
• The special move member functions disable the normal copying and assignment. The copying and other
moving special member functions are deleted so that you can only move (assign) but not copy (assign)
an object (unless the other operations are also declared).
Let us look at some details.
// NO destructor declared
};
In this case, a Person can be both copied and moved:
std::vector<Person> coll;
Because the fallback mechanism works, copying and moving a Person compiles but the move is performed
as a copy:
std::vector<Person> coll;
A class supporting move operations but not copy operations can make sense. You can use such a move-
only type to pass around ownership or handles of resources without sharing or copying them. In the
C++ standard library there are a couple of move-only types (e.g., I/O stream classes, thread classes, and
std::unique_ptr<>). See the chapter about move-only types for details.
In this case, =delete disables the fallback mechanism (and is therefore also not the same as not declaring
it at all). The compiler finds the declaration and reports the call as an error.
A type that supports copying but fails when moving is called does not make any sense. For the user of
such a class, copying would sometimes work, sometimes not. As a guideline: never =delete the special
move member functions.4 If you want to disable both copying and moving, deleting the copying special
member functions is enough.
Copy Constructor
The copy constructor is automatically generated when all of the following apply:5
• No move constructor is user-declared
• No move assignment operator is user-declared
If generated (implicitly or with =default), the copy constructor has the following behavior:6
MyClass(const MyClass& obj) noexcept-specifier
: Base(obj), value(obj.value) {
}
The generated copy constructor first passes the source object, to the best matching copy constructor of the
base class(es). (remember that copy constructors are always called on a top-down basis). It prefers the copy
constructor with the same declaration (usually declared const&), but if that is not available it might call the
next best matching constructor (e.g., a copy constructor template). Afterwards, it copies all members of its
class (again using the best match).
The generated copy constructor is declared as noexcept if all copy operations (the copy constructors of
all base classes and the copy constructors of all members) give this guarantee.
Move Constructor
The move constructor is automatically generated when all of the following apply:
• No copy constructor is user-declared
• No copy assignment operator is user-declared
• No move assignment operator is user-declared
• No destructor is user-declared
If generated (implicitly or with =default), the move constructor has the following behavior:
MyClass(MyClass&& obj) noexcept-specifier
: Base(std::move(obj)), value(std::move(obj.value)) {
}
The generated move constructor first passes the source object, marked with std::move() to pass through
its move semantics, to the best matching move constructor of the base class(es). The best matching move
constructor usually is the one with the same declaration (declared with &&). However, if that is not available it
might call the next best matching constructor (e.g., a move constructor template or even a copy constructor).
Afterwards, it moves all members of its class (again using the best match).
The generated move constructor is declared as noexcept if all called move/copy operations (the copy or
move constructors of all base classes and the copy or move constructors of all members) give this guarantee.
5 Since C++11, the copy constructor is deprecated if the copy assignment operator or the destructor is user-declared.
6 The generated copy constructor takes the argument as a non-const reference if one of the copy constructors used is
implemented without const.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 56
7 Since C++11, the copy assignment operator is deprecated if the copy constructor or the destructor is user-declared.
8 The generated copy assignment operator takes the argument as a non-const reference if one of the copy assignment
operators used is implemented without const.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 57
You might wonder why we still use members of the source object obj after the object was marked with
std::move():
Base::operator=(std::move(obj)); // - perform move assignments for base class members
However, in this case we mark the object for the specific context of a base class, which cannot see the
members introduced in this class. Therefore, the derived members have a valid but unspecified state but we
can still use the values of the new members.
The generated assignment operator also does not check for assignments of objects to themselves. Thus, in
its default behavior, the operator will move assign each member to itself, which usually means that members
receive a valid but unspecified value. If this is critical, you have to implement the operator yourself.
In addition, the generated move assignment operator is declared as noexcept if all called assignment
operations (the assignments for base class members and the assignments for new members) give this guar-
antee.
That is, when one of these special member functions is either implemented or defaulted or deleted, you
should implement or default or delete all four other special member functions.
However, you should be careful with this rule. I recommend that you take it more as a guideline to
carefully think about all of these five special member functions when one of them is user-declared.
As we saw, to enable copy semantics only you should =default the copying special member functions
without declaring the special move member functions (deleting and defaulting the special move member
functions would not work, implementing them makes the class unnecessarily complicated). This option
is recommended in particular if the generated move semantics creates invalid states, as we discuss in the
section about invalid moved-from states.
When applying this rule of five, it also turned out that sometimes, programmers use it to add declarations
for the new move operations without understanding what this means. Programmers were just declaring move
operations with =default because copy operations were implemented and they wanted to follow the rule
of five.
Therefore, I usually teach the Rule of Five or Three:
• If you declare the copy constructor, move constructor, copy assignment operator, move assignment oper-
ator, or destructor, think very carefully about how to deal with the other special member functions.
• If you do not understand move semantics, think only about the copy constructor, copy assignment opera-
tor, and destructor if you are declaring one of them. If in doubt, disable move semantics by declaring the
copying special member functions with =default.
3.5 Summary
• Move semantics is not passed through.
• For every class, the move constructor and move assignment operator are automatically generated (unless
there is no way to do so).
• User-declaring a copy constructor, copy assignment operator, or destructor disables the automatic support
of move semantics in a class. This does not impact the support in derived classes.
• User-declaring a move constructor or move assignment operator disables the automatic support of copy
semantics in a class. You get move-only types (unless these special move member functions are deleted).
• Never =delete a special move member function.
• Do not declare a destructor if there is no specific need. There is no general need in classes derived from
a polymorphic base class.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 59
Chapter 4
How to Benefit From Move Semantics
Most of the time, programmers benefit from move semantics automatically. However, even as an ordi-
nary application programmer, you can benefit from move semantics even more when programming slightly
differently.
This chapter discusses how to benefit from move semantics in ordinary application code, classes, and
class hierarchies beyond the automatic generation of special move member functions. The chapter also
introduces corresponding new guidelines.
Note that even several years after introducing move semantics these recommendations are not widely
known in the community. In fact, one reason for me to write this book was to make them state of the art for
modern C++ programming.
59
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 60
Of course, the advice to avoid objects with names might conflict with other important style guidelines
such as readability and maintainability of source code. Instead of having a complex statement, it might be
better to use multiple statements. In that case, you should use std::move() if you no longer need an object
(and know that copying the object might take significant time):
foo(std::move(x));
std::string foo()
{
std::string s;
...
return s; // best performance (return value optimization or move)
}
Using std::move() when you already have a temporary object is at least redundant. For a function
createString() that returns an object by value, you should just use the return value:
std::string s{createString()}; // OK
instead of marking it with std::move() again:
std::string s{std::move(createString())}; // BAD: don’t do this
Compilers might (have options to) warn about any counterproductive or unnecessary use of std::move().
For example, gcc has the options -Wpessimizing-move (enabled with -Wall) and -Wredundant-move
(enabled with -Wextra).
There are applications, though, where a std::move() in a return statement might be appropriate. One
example is moving out the value of a member. Another example is returning a parameter with move seman-
tics.
class Person {
private:
std::string first; // first name
std::string last; // last name
public:
Person(const std::string& f, const std::string& l)
: first{f}, last{l} {
}
...
};
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 62
Now let us look at what happens when we initialize an object of this class with two string literals:
Person p{"Ben", "Cook"};
The compiler finds out that the provided constructor can perform the initialization. However, the types of the
parameters do not fit. Therefore, the compiler generates code to first create two temporary std::strings,
which are initialized by the values of the two string literals, and binds the parameters f and l to them:
In general (if the small string optimization (SSO) is not available or the strings are too long), this means that
code is generated that allocates memory for the value of each std::string.
However, the temporary strings created are not used directly as members first or last. Instead, they
are used to initialize these members. Unfortunately, move semantics is not used here for two reasons:
• The parameters f and l, are objects with names that exist for a longer period than the initialization of the
members (you can still use them in the body of the constructor).
• The parameters are declared to be const, which disables move semantics even if we use std::move().
As a consequence, the copy constructor for strings is called on each member initialization, again allocating
memory for the values:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 63
This means that we have four memory allocations although only two are necessary. Using move semantics
we can do better.
class Person {
private:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 64
By using std::move(), we move the values of the parameters to the members. First, the member first
steals the value from f:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 65
Again, at the end of the constructor, the temporary strings are destroyed. This time it takes less time because
the destructors of the strings no longer have to free allocated memory:
This way to initialize the members also works fine if we pass std::strings:
• If we pass two existing strings without marking them with std::move(), we copy the names to the
parameters and move them to the members:
std::string name1{"Jane"}, name2{"White"};
...
Person p{name1, name2}; // OK, copy names into parameters and move them to the members
• If we pass two strings where the value is no longer needed, we do not need any allocation at all:
std::string firstname{"Jane"};
...
Person p{std::move(firstname), // OK, move names via parameters to members
getLastnameAsString()};
In this case we move the passed strings twice: once to initialize the parameters f and l and once to move
the values of f and l to the members.
Provided a move is cheap, with this implementation of only one constructor any initialization is possible and
cheap.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 66
And because we have non-const references, we can modify them. In this case, we mark them with
std::move() so that the initialization of the members can steal the values.
First, the member first steals the value from f:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 67
Again, at the end of the constructor, the temporary strings are destroyed without the need to free allocated
memory:
class Person {
private:
std::string first; // first name
std::string last; // last name
public:
Person(const std::string& f, const std::string& l)
: first{f}, last{l} {
}
Person(const std::string& f, std::string&& l)
: first{f}, last{std::move(l)} {
}
Person(std::string&& f, const std::string& l)
: first{std::move(f)}, last{l} {
}
Person(std::string&& f, std::string&& l)
: first{std::move(f)}, last{std::move(l)} {
}
Person(const char* f, const char* l)
: first{f}, last{l} {
}
Person(const char* f, const std::string& l)
: first{f}, last{l} {
}
Person(const char* f, std::string&& l)
: first{f}, last{std::move(l)} {
}
Person(const std::string& f, const char* l)
: first{f}, last{l} {
}
Person(std::string&& f, const char* l)
: first{std::move(f)}, last{l} {
}
...
};
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 70
The benefit of this solution is that we reduce the number of moves. If we pass a string literal, we initialize
the members directly with the passed pointer instead of creating a std::string and moving its value to the
member.
The function measure() returns the duration of num iterations performing the three initializations above
with strings of a significant length.
Now we combine the different definitions of the class Person above with the measuring function in
programs with main() functions that call the measurements and print the resulting durations. For example:
basics/initclassicperf.cpp
#include "initclassic.hpp"
#include "initmeasure.hpp"
#include <iostream>
#include <cstdlib> // for std::atoi()
// print result:
std::cout << num << " iterations take: "
<< msDur.count() << "ms\n";
std::cout << "3 inits take on average: "
<< nsDur.count() / num << "ns\n";
}
The other two programs, basics/initallperf.cpp and basics/initmoveperf.cpp, just use different
header files for the other declarations of the class Person.
The effect of running this code on three different platforms with three different compilers with significant
optimization activated are as follows:
• In general, the initializations using the classic lvalue references (const &) take significantly more time
than the other initializations. I have seen factors of up to 2.
• There is no big difference between implementing all nine constructors and just the constructor taking the
argument by value and move. Sometimes one approach was a little faster, sometimes the other; often
there was no significant difference.
If we benefit from the small string optimization (SSO), by using quite short strings, which means that we do
not allocate any memory at all (and move semantics should be no significant help), the numbers are quite
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 72
close. Nevertheless, you can still measure a slight drawback using the traditional constructor that takes a
const lvalue reference. However, this definitely requires that good optimization is activated.
Besides trying the different test programs, you can use basics/initperf.cpp as one combined pro-
gram that performs all these different measurements on your favorite platform.
However, you might have very expensive members that do not benefit from move semantics (such as an
array of 10000 double values):
class Person {
private:
std::string name;
std::array<double, 10000> values; // move can’t optimize here
public:
...
};
In such a situation, we get a problem with the approach of taking an initial argument of 10000 doubles
by value and move. We have to copy the argument twice, which takes almost twice as much time. See
basics/initbigperf.cpp for a complete example program to measure this.
private:
std::string name;
std::array<std::string, 1000> values;
public:
Person(std::string n, const std::array<std::string, 1000>& v)
: name{std::move(n)}, values{v} {
}
Person(std::string n, std::array<std::string, 1000>&& v)
: name{std::move(n)}, values{std::move(v)} {
}
...
};
p.setFirstname(name1);
p.setFirstname(name2);
p.setFirstname(name1);
p.setFirstname(name2);
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 74
Each time we set a new firstname we create a new temporary parameter s which allocates its own memory,
which is then moved to the value of the member. Thus, we have four allocations (provided we do not have
SSO).
Now consider that we implement the setter in the traditional way taking a const lvalue reference:
class Person {
private:
std::string first; // first name
std::string last; // last name
public:
Person(std::string f, std::string l)
: first{std::move(f)}, last{std::move(l)} {
}
...
void setFirstname(const std::string& s) { // take by reference
first = s; // and assign
}
...
};
Binding s to the passed argument will not create a new string. Furthermore, the assignment operator will
only allocate new memory if the new length exceeds the current amount of memory allocated for the value.
This means that, because we already have a value, the approach of taking the argument by value and move
might be counterproductive.
You might wonder whether to overload the setter so that we can benefit from move semantics if the new
length exceeds the existing length:
class Person {
private:
std::string first; // first name
std::string last; // last name
public:
Person(std::string f, std::string l)
: first{std::move(f)}, last{std::move(l)} {
}
...
void setFirstname(const std::string& s) { // take by lvalue reference
first = s; // and assign
}
void setFirstname(std::string&& s) { // take by rvalue reference
first = std::move(s); // and move assign
}
...
};
However, even this approach might be counterproductive, because a move assignment might shrink the
capacity of first:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 75
GeoObj& geoRef{c1};
geoRef = c2; // OOPS: uses GeoObj::operator=() and assigns no Circle members
Because we call the assignment operator for a GeoObj and the operator is not virtual, the compiler calls
GeoObj::operator=(), which does not deal with any member of any derived class. Even declaring the
assignment operator with virtual would not help, because an operator of the derived class does not override
the assignment operator of the base class (the parameter types for the second operand differ).
To avoid this problem, you should disable the use of the assignment operator in polymorphic class hier-
archies. Furthermore, if the class is not abstract, you should also avoid having public copy constructors to
disable implicit type conversions to the base class. Therefore, a polymorphic base class with move semantics
(and members) should be declared as follows:
class GeoObj {
protected:
std::string name; // name of the geometric object
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 77
GeoObj(std::string n)
: name{std::move(n)} {
}
public:
virtual void draw() const = 0; // pure virtual function (introducing the API)
...
virtual ~GeoObj() = default; // would disable move semantics for name
protected:
// enable copy and move semantics (callable only for derived classes):
GeoObj(const GeoObj&) = default;
GeoObj(GeoObj&&) = default;
// disable assignment operator (due to the problem of slicing):
GeoObj& operator= (GeoObj&&) = delete;
GeoObj& operator= (const GeoObj&) = delete;
};
See poly/geoobj.hpp for the complete header file.
1 virtual is not necessary if member functions are declared with override. However, I prefer to have it again for
better alignment and the rule that either all or no member functions should be virtual.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 78
However, without declaring the destructor, move semantics works for both Polygon members, name and
points. Consider the following program:
poly/polygon.cpp
#include "geoobj.hpp"
#include "polygon.hpp"
int main()
{
Polygon p0{"Poly1", {Coord{1,1}, Coord{1,9}, Coord{9,9}, Coord{9,1}}};
Polygon p1{p0}; // copy
Polygon p2{std::move(p0)}; // move
p0.draw();
p1.draw();
p2.draw();
}
This program has the following output:
polygon ’’ over
polygon ’Poly1’ over (1,1) (1,9) (9,9) (9,1)
polygon ’Poly1’ over (1,1) (1,9) (9,9) (9,1)
For both members, name and points, the values were moved from p0 to p2.
Note that if you have to implement the move constructor in class Polygon, you need special care to
provide the right noexcept condition.
4.5 Summary
• Avoid objects with names.
• Avoid unnecessary std::move(). Especially do not use it when returning a local object.
• Constructors that initialize members from parameters, for which move operations are cheap, should take
the argument by value and move it to the member.
• Constructors that initialize members from parameters, for which move operations take a significant
amount of time, should be overloaded for move semantics for best performance.
• In general, creating and initializing new values from parameters, for which move operations are cheap,
should take the arguments by value and move. However, do not take by value and move to update/modify
existing values.
• Do not declare a virtual destructor in derived classes (unless you have to implement it).
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 79
Chapter 5
Overloading on Reference Qualifiers
This chapter discusses overloading member functions for different reference qualifiers. As we will see, this
will answer the common community question about whether getters should return by value or by constant
reference.
79
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 80
For example, just checking whether we have a person with an empty name would have significant over-
head:
std::vector<Person> coll;
...
for (const auto& person : coll) {
if (person.getName().empty()) { // OOPS: copies the name
std::cout << "found empty name\n";
}
}
If you compare this approach with an approach that returns a reference, you can see that the version that
returns the string by value has a performance overhead of a factor between 2 and 100 (provided the names
have a significant length so that SSO does not help). Giving access to a member that is an image or a
collection of thousands of elements might be even worse. In that case, getters often return by (const)
reference to improve the performance.
char c = *pos;
if (c == ' ') {
...
}
}
Before we start to iterate, we initialize a reference1 because we have to use the passed range twice (once to
call begin() and once to call end() for it) and want to avoid creating a copy of the range (which might be
expensive or even not possible). In general, references extend the lifetime of what they refer to. However, in
this case, range does not refer to the Person returned by returnPersonByValue(); range refers to the
return value of getName(), which is a reference to a returned Person. Thus, range extends the lifetime
of the reference but not of the temporary object that the reference refers to. Therefore, with the end of the
first statement, the returned temporary object is destroyed and we use a reference to the name of a destroyed
object when we iterate over the characters of the name.
At best, we get a core dump here so that we see that something went significantly wrong. At worst, we
get fatal undefined behavior once we ship the software.
Code like this would not be a problem if the getter were to return the name by value. In that case, range
would extend the lifetime of a copy of the name so that we can use the name until the end of the lifetime of
range.
1 The exact type of the reference is auto&& for reasons we discuss later
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 82
• The version with the const& qualifier is used in all other cases. It always fits but is only the fallback if
we cannot take the && version. Thus, this function is used if we have an object that is not about to die or
marked with std::move().
Now we have both good performance and safety:
Person p{"Ben"};
std::cout << p.getName(); // 1) fast (returns reference)
std::cout << returnPersonByValue().getName(); // 2) fast (uses move())
std::vector<Person> coll;
...
for (const auto& person : coll) {
if (person.getName().empty()) { // 3) fast (returns reference)
std::cout << "found empty name\n";
}
}
Using std::move() when we call getName() will improve the performance of this program. Instead of
returning a reference to a const std::string, which we can only copy, the return value is the moved name
of p returned as a non-const string so that push_back() can use move semantics to move it into coll.
As usual, after this call, p is in a valid but unspecified state.
For an example where this feature is used in the C++ standard library, see class std::optional<>.
class C {
public:
void foo() const& {
std::cout << "foo() const&\n";
}
void foo() && {
std::cout << "foo() &&\n";
}
void foo() & {
std::cout << "foo() &\n";
}
void foo() const&& {
std::cout << "foo() const&&\n";
}
};
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 84
int main()
{
C x;
x.foo(); // calls foo() &
C{}.foo(); // calls foo() &&
std::move(x).foo(); // calls foo() &&
const C cx;
cx.foo(); // calls foo() const&
std::move(cx).foo(); // calls foo() const&&
}
This program demonstrates all reference qualifiers that are possible and when they are called. Usually, we
have only two or three of these overloads, such as using && and const& (and &) for getters.
Also note that overloading for both reference and non-reference qualifiers it is not allowed:
class C {
public:
void foo() &&;
void foo() const; // ERROR: can’t overload by both reference and value qualifiers
};
For example, the assignment operators for strings are declared as follows:
namespace std {
template<typename charT, ... >
class basic_string {
public:
...
constexpr basic_string& operator=(const basic_string& str);
constexpr basic_string& operator=(basic_string&& str) noexcept( ... );
constexpr basic_string& operator=(const charT* s);
...
};
}
This enables accidental assignment of a new value to a temporary string:
std::string getString();
getString() = "hello"; // OK
foo(getString() = ""); // passes string instead of bool
Consider we declare the assignment operators with reference qualifiers instead:
namespace std {
template<typename charT, ... >
class basic_string {
public:
...
constexpr basic_string& operator=(const basic_string& str) &;
constexpr basic_string& operator=(basic_string&& str) & noexcept( ... );
constexpr basic_string& operator=(const charT* s) &;
...
};
}
Code like that would no longer compile:
std::string getString();
Essentially, we give temporary objects back a property that they have for fundamental types: they are rvalues,
which means that they cannot be on the left-hand side of an assignment.
Note that all of these proposals to fix the C++ standard accordingly have so fare been rejected. The main
reason was concerns about backward compatibility. However, when implementing your own class, you can
use this improvement as follows:
class MyType {
public:
...
// disable assigning value to temporary objects:
MyType& operator=(const MyType& str) & =default;
MyType& operator=(MyType&& str) & =default;
5.4 Summary 87
5.4 Summary
• You can overload member functions on different reference qualifiers.
• Overload getters for expensive members with reference qualifiers to make them both safe and fast.
• It can make sense to mark objects with std::move() even when calling member functions.
• Use reference qualifiers in assignment operators.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 88
88
Chapter 6
Moved-From States
Although move semantics as generated or naively implemented usually works fine, we should at least have
a look at the possible case of a move operation bringing objects into a state that is not supported by the C++
standard library or breaks invariants of the type.
In this chapter, we clarify the definition of an “invalid” state according to the guarantee of the C++
standard library that moved-from objects are in a valid but unspecified state.
89
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 90
Note that we might also have self-swapping by passing the same object to both parameters. In that case, we
might even assign an object with a moved-from state to itself. This all should work (provided a moved-from
object still can have any value).
So, for moved-from objects we have the same basic requirements that usually apply to all objects:
• We have to be able to destroy moved-from objects.
• We have to be able to assign a new value to moved-from objects.
• We should be able to copy, move, or assign a moved-from objects to another object.
Moved-from objects should also be able to deal with additional requirements particular operations have. For
example, to sort objects we have to support calling operator< or the sorting criterion for all objects. This
also applies to moved-from objects. You might argue that in your sorting algorithm you should know which
object was moved so that you can avoid comparing it, but the C++ standard library does not require that.
This, by the way, also means that you can pass moved-from objects to sort them. As long as they support all
required operations, everything is fine.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 91
Note that the definition of the C++ standard library requires more than what is called partially formed in
“Elements of Programming” by Alexander Stepanov and Paul McJones.2
For all objects and types you use in the C++ standard library you should ensure that moved-from objects
also support all requirements of the functions called.
2 See http://elementsofprogramming.com.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 92
The value of an object is not specified except that the object’s invariants are met and operations
on the object behave as specified for its type
Invariants are the guarantees that apply to all of the objects that can be created. With this guarantee, you
can assume that a moved-from object is in a state that means that its invariants are not broken. You can use
the object like a non-const reference parameter without knowing anything about the argument passed: you
can call any operation that has no constraint or precondition and the effect/result of this call is as specified
for any other object of this type.
For example, for a moved-from string, we can perform all operations that have no precondition:
• Ask for its size
• Print it out
• Iterate over the characters
• Convert it to a C string
• Assign a new value
• Append a character
Furthermore, all of these functions still have the usual specified semantics:
• The returned size can be used to safely call the index operator.
• The returned size matches the number of characters when iterating over them.
• The printed characters match the sequence of characters when iterating over them.
• Appending a character will place that character at the end of the value (without knowing whether and
which other characters are in front).
Programmers can make use of these guarantees (see the std::stack<> example above).
However, sometimes you have to explicitly ensure that intended invariants are not broken. The default
special move member functions might not work properly. We will discuss examples in the following sub-
sections.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 93
Restricted Invariants
It might also be useful not to give the full guarantee of the C++ standard library to moved-from states. That
is, you might intentionally restrict the possible operations for moved-from objects. For example, when a
valid state of an object always needs resources such as memory, having only a partially supported state
might be better to make move operations cheaper.
We have such a case in the C++ standard library: some implementations always need memory for all states
of node-based containers (so that even the default constructor has to allocate memory). For these containers,
it might have been better to restrict what we can do with moved-from objects instead of guaranteeing that
moved-from objects are always in a “valid” state. However, we decided to give the full guarantee.
Ideally, a moved-from state that does not support all operations should be detectable. The objects should
know this state and provide a member function to check for this state. Moved-from objects might also refuse
to execute operations not supported in this state. However, corresponding checks might cost performance in
the general case.
In the C++ standard library, some types provide APIs to check whether objects are in a moved-from state.
For example, std::futures have a member function valid() that returns false for moved-from objects.
But the interfaces to check for moved-from states vary.
Pretty often the moved-from state is the default constructed state, which means that the moved-from state
is parts of the invariants anyway. In any case, make sure users of your types know about what is well-defined
and what is not.
When the value of a customer is moved away, both name and values are guaranteed to have a valid state so
that the destructor for them (called by the destructor of Customer) works fine:
void foo()
{
Customer c{"Michael Spencer"};
...
process(std::move(c));
// both name and values have valid but unspecified states
...
} // destructor of c will clean up name and values (whatever their state is)
Also, assigning a new value to c will work because we assign both the name and the values.
class Tasks {
private:
std::array<std::thread,10> threads; // array of threads for up to 10 tasks
int numThreads{0}; // current number of threads/tasks
public:
Tasks() = default;
3 We see here one reason why move semantics is disabled by default when a destructor is implemented.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 95
}
}
};
So far, the class does not support move semantics because we have a user-declared destructor. You also
cannot copy Tasks objects because copying std::threads is disabled. However, you can start multiple
tasks and wait for their end:
basics/tasks.cpp
#include "tasks.hpp"
#include <iostream>
#include <chrono>
int main()
{
Tasks ts;
ts.start([]{
std::this_thread::sleep_for(std::chrono::seconds{2});
std::cout << "\nt1 done" << std::endl;
});
ts.start([]{
std::cout << "\nt2 done" << std::endl;
});
}
Now consider enabling move semantics by generating the default implementation of the move operations
(see basics/tasksbug.cpp):
class Tasks {
private:
std::array<std::thread,10> threads; // array of threads for up to 10 tasks
int numThreads{0}; // current number of threads/tasks
public:
...
// OOPS: enable default move semantics:
Tasks(Tasks&&) = default;
Tasks& operator=(Tasks&&) = default;
In this case, you are in trouble because the default generated move operation may create invalid Tasks
states. Consider the following example:
basics/tasksbug.cpp
#include "tasksbug.hpp"
#include <iostream>
#include <chrono>
#include <exception>
int main()
{
try {
Tasks ts;
ts.start([]{
std::this_thread::sleep_for(std::chrono::seconds{2});
std::cout << "\nt1 done" << std::endl;
});
ts.start([]{
std::cout << "\nt2 done" << std::endl;
});
• Disabling move semantics (which would be the behavior here without declaring special move member
functions)
According to the Rule of Zero, you should encapsulate error-prone resource management in a helper type so
that application programmers have to implement zero special member functions. In this case, you might use
a helper class (template) that provides both members (the std::array and a member for the actual number
of elements used) and a correct implementations of the move operations.
The whole problem is also a side effect of a design mistake of class std::thread. The type does
not follow the RAII principle. For all std::threads with running threads, you have to call join() (or
detach()) before their destructor is called; otherwise the destructor of a thread throws. Since C++20, you
could and should use class std::jthread instead, which will automatically call join() if the object still
represents a running thread.
Consider a class for the cards of a card game.4 Assume each object is a valid card, such as eight-of-hearts
or king-of-diamonds. Assume also that for whatever reason, the value is a string and that the invariant of
the class is that each and every object has a state that represents a valid card. This would mean that we
probably do not have a default constructor and that an initializing constructor asserts that the value is valid.
For example:
class Card {
private:
std::string value; // rank + "-of-" + suit
public:
Card(const std::string& v)
: value{v} {
assertValidCard(value); // ensure the value is always valid
}
This code might fail at runtime because for a moved-from card, there it is no longer a guarantee that the
value contains "-of-". In that case, find() initializes pos with std::string::npos, which throws an
exception of type std::out_of_range when pos+4 is used as the first argument of substr().
See basics/card.hpp and basics/card.cpp for the full example.
The options for fixing this class are as follows:
• Disable move semantics:
class Card {
...
Card(const Card&) = default; // disable move semantics
Card& operator=(const Card&) = default; // disable move semantics
};
However, that makes move operations (which, for example, are called by std::sort()) more expensive.
• Disable copying and moving at all:
class Card {
...
Card(const Card&) = delete; // disable copy/move semantics
Card& operator=(const Card&) = delete; // disable copy/move semantics
};
However, you can then no longer shuffle or sort cards.
• Fix the broken special move member functions.
However, what would be a valid fix (is always assigning a “default value” such as "ace-of-clubs"
OK)? And how do you ensure that objects with the default value perform well without allocating memory?
• Internally allow the new state but disallow calling getValue() or other member functions.
You can document this (“For moved-from objects, you are only allowed to assign a new value. All
other member functions have the precondition that the object is not in a moved-from state.”) or even
check this inside the member functions and raise an assertion or an exception.
• Extend the invariant by introducing a new state that a Card might have no value.
This means that you have to implement the moving special member functions because you have to
ensure that for a moved-from object, the member value is in this state.
Usually, a moved-from state is equivalent to a default-constructed state. Therefore, this is also an
opportunity to provide a default constructor. Ideally, you might also provide a member function, checking
for this state.
With this change, the users of this class have to take into account that the value of the string might be
empty and update their code accordingly. For example:
void print(const Card& c) {
std::string val{c.getValue()};
auto pos = val.find("-of-"); // find position of substring
If (pos != std::string::npos) { // check whether it exists
std::cout << val.substr(0, pos) << ' '
<< val.substr(pos+4) << '\n';
}
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 100
else {
std::cout << "no value\n";
}
}
Alternatively, getValue() might return a std::optional<std::string> (available since C++17).
It seems that there is no obvious perfect solution. You have to think about what each of these fixes mean
to the larger invariants of your program (i.e., that there is only one ace-of-clubs, or that all cards are valid
cards, etc.) and decide which one to use.
Note that this class worked fine before C++11, where move semantics was not supported (which might
mean the first option is the best one). Thus, C++11 might introduce states for classes that were not possible
when the class was implemented. It is a rare case but it does mean that the introduction of move semantics
could break existing code.
See class Email for another example of a class where we internally mark the moved-from state to handle
it separately and make this state visible after a “removing” algorithm that leaves elements in a moved-from
state.
class IntString
{
private:
int val; // value
std::string sval; // cached string representation of the value
public:
IntString(int i = 0)
: val{i}, sval{std::to_string(i)} {
}
void setValue(int i) {
val = i;
sval = std::to_string(i);
}
...
void dump() const {
std::cout << " [" << val << "/'" << sval << "']\n";
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 101
}
};
In this class, we usually make sure that the member val and the member sval are just two different rep-
resentations of the same value. That means that in the implementation and use of this class, we usually
expect that both the int and the string representation of its state are consistent. However, if we call a
move operation here, we will keep the value val, but sval will no longer be guaranteed to have the string
representation of val.
Consider the following program:
basics/intstring.cpp
#include "intstring.hpp"
#include <iostream>
int main()
{
IntString is1{42};
IntString is2;
std::cout << "is1 and is2 before move:\n";
is1.dump();
is2.dump();
is2 = std::move(is1);
The fact is that with corresponding getters, the class no longer guarantees that the value as int and
string match, which is probably a class invariant here (implicitly or explicitly stated). You might think
that the worst consequence is that the value (which is unspecified now) looks different depending on how
you use it, but there is no problem using it because it is still a valid int or string.
However, code counting on this invariant might be broken. That code might assume that the string
representation has at least one digit. For example, if it searches for the first or last digit, you will definitely
find one. For a moved-from string, which is typically empty, this is no longer the case. Therefore, code
not double-checking whether there is any character in the string value, might run into unexpected undefined
behavior.
Again, it is up to the designer of the class how to deal with this problem. However, if you follow the rule
of the C++ standard library, you should leave your moved-from object in a valid state, which might mean
that you introduce a possible state that represents “I do not have any value.”
In general, when the state of an object has members that depend on each other in some way, you have to
explicitly ensure that the moved-from state is in a valid state. Examples where this might be broken are:
• We have different representations of the same value but some of them were moved away.
• A member such as a counter corresponds with the number of elements in a member.
• A Boolean value claims that a string value was validated but the validated value was moved away.
• A cached value for the average values of all elements is still there but the values (being in a container
member) were moved away.
Note again that this class worked fine before C++11, where move semantics was not supported. The invariant
is broken when switching to C++11 or later and moved-from objects are used.
5 Thanks to Geoffrey Romer and Herb Sutter for providing the idea for this example in an email discussion of the C++
standard library working group.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 103
The objects of this class receive an initial integral value that can be shared with copies of these objects. As
long as new objects are only copied everything is fine:
SharedInt si1{42};
SharedInt si2{si1}; // si1 and si2 share the value 42
std::cout << si1.asString() << '\n'; // undefined behavior (probably core dump)
The problem is that inside the class, we do not deal correctly with the fact that the value might have been
moved away, which can happen because the default generated move operations call the move operations of
the shared pointer, which moves the ownership away from the original object. This means that the moved-
from state of a SharedInt brings the member sp into the situation that it no longer owns an object, which
is not handled properly in its member function asString().
You might argue that calling asString() for an object with a moved-from state makes no sense because
you are using an unspecified value, but at least the standard library guarantees for its types that moved-from
types are in a valid state so that you can call all operations that have no constraints. Not giving the same
guarantee in a user-defined type can be surprising for users of the type.
From a perspective of robust programming (avoiding surprises, traps, and undefined behavior), I would
usually recommend that you follow the rule of the C++ standard library. That is: move operations should
not bring objects into a state that breaks invariants.
In this case, we have to do one of the following:
• Fix all broken operations of the class by also dealing correctly with all possible moved-from states
• Disable move semantics so that there is no optimization when copying objects
• Implement move operations explicitly
• Adjust and document the invariant (constraints/preconditions) for the class or specific operations (such as
“It is undefined behavior to call asString() for a moved-from-object”)
Because allocating memory is expensive, probably the best fix in this case is to deal correctly with the fact
the ownership of the integral value might be moved away. That would create a state that a default constructor
would have, which we could introduce with this change.
The following subsections demonstrate this and the other code fixes.
design decisions. For example, when calling asString() for a moved-from object (or more generally, an
object where the shared pointer does not own an integral value), we can:
• Still return a fallback value:
class SharedInt {
...
std::string asString() const {
return sp ? std::to_string(*sp) : "";
}
...
};
• Throw an exception:
class SharedInt {
...
std::string asString() const {
if (!sp) throw ...
return std::to_string(*sp);
}
...
};
• Force a runtime error in debug mode:
class SharedInt {
...
std::string asString() const {
assert(sp);
return std::to_string(*sp);
}
...
};
class SharedInt {
private:
std::shared_ptr<int> sp;
// special “value” for moved-from objects:
inline static std::shared_ptr<int> movedFromValue{std::make_shared<int>(0)};
public:
explicit SharedInt(int val)
: sp{std::make_shared<int>(val)} {
}
6 Note that inline static members are only supported since C++17. Before C++17, you had to define the member
in a separate CPP file to respect the one definition rule (ODR).
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 106
6.4 Summary
• For each class, clarify the state of moved-from objects. You have to ensure that they are at least destruc-
tible (which is usually the case without implementing special member functions). However, users of your
class might expect/require more.
• The requirements of functions of the C++ standard library also apply to moved-from objects.
• Generated special move member functions might bring moved-from objects into a state such that a class
invariant is broken. This might happen especially if:
– Classes have no default constructor with a determinate value (and therefore no natural moved-from
state)
– Values of members have restrictions (such as assertions)
– Values of members depend on each other
– Members with pointer-like semantics are used (pointers, smart pointers, etc.)
• If the moved-from state breaks invariants or invalidates operations, you should fix this by using one of
the following options:
– Disable move semantics
– Fix the implementation of move semantics
– Deal with broken invariants inside the class and hide them to the outside
– Relax the invariants of the class by documenting the constraints and preconditions for moved-from
objects
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 107
Chapter 7
Move Semantics and noexcept
When move semantics was almost complete for C++11, we detected a problem: vector reallocations could
not use move semantics. As a consequence, the new keyword noexcept was introduced.
This chapter explains the problem and what this means for the use of noexcept in C++ code.
class Person {
private:
std::string name;
public:
Person(const char* n)
: name{n} {
}
107
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 108
int main()
{
std::vector<Person> coll{"Wolfgang Amadeus Mozart",
"Johann Sebastian Bach",
"Ludwig van Beethoven"};
std::cout << "capacity: " << coll.capacity() << '\n';
coll.push_back("Pjotr Iljitsch Tschaikowski");
}
The output of the program is as follows:
COPY Wolfgang Amadeus Mozart
COPY Johann Sebastian Bach
COPY Ludwig van Beethoven
capacity: 3
MOVE Pjotr Iljitsch Tschaikowski
COPY Wolfgang Amadeus Mozart
COPY Johann Sebastian Bach
COPY Ludwig van Beethoven
We first copy the initial values into the vector (because the std::initializer_list<> constructor of a
container takes the passed arguments by value). As a result, the vector typically allocates memory for three
elements (in the figure, I use shortcuts for the string values):
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 109
The important thing here is what happens next: We insert a fourth element with push_back(), which
has the following consequences:
• Because the vector needs more memory internally, it allocates the new memory (for say, six elements),
moves in the fourth string (we create a temporary Person and move it into the vector with push_back()),
but also copies the existing elements to the new memory:
• At the end of this operation the vector destroys the old elements, releases the old memory for theses
elements, and updates its members:
The question is, why does the vector not use the move constructor to move the elements from the old to the
new memory?
memory to bring the vector back to its previous state (the C++ standard library assumes and requires that
destructors do not throw; otherwise, it would not be able to roll back).
Reallocation is a perfect place for move semantics because we move elements from one location to the
other. Therefore, since C++11, we want to use move semantics here. However, then we are in trouble: if
an exception is thrown during the reallocation, we might not be able to roll back. The elements in the new
memory have already stolen the values of the elements in the old memory. Therefore, destroying the new
elements would not be enough; we have to move them back. But how do we know that moving them back
does not fail?
You might argue that a move constructor should never throw. This might be correct for strings (because
we just move integral values and pointers around), but because we require that moved-from objects are in a
valid state, this state may need memory, which means that the move might throw if we are out of memory
(e.g., node-based containers of Visual C++ are implemented that way).
We also cannot give up the guarantee because programs might have used this feature to avoid creating a
backup of a vector and losing that data could be (safety) critical. And no longer supporting push_back()
would be a nightmare for the acceptance of C++11.
The final decision was to use move semantics on reallocation only when the move constructor of the
element types guarantees not to throw.
class Person {
private:
std::string name;
public:
Person(const char* n)
: name{n} {
}
int main()
{
std::vector<Person> coll{"Wolfgang Amadeus Mozart",
"Johann Sebastian Bach",
"Ludwig van Beethoven"};
std::cout << "capacity: " << coll.capacity() << '\n';
coll.push_back("Pjotr Iljitsch Tschaikowski");
}
we get the following output:
COPY Wolfgang Amadeus Mozart
COPY Johann Sebastian Bach
COPY Ludwig van Beethoven
capacity: 3
MOVE Pjotr Iljitsch Tschaikowski
MOVE Wolfgang Amadeus Mozart
MOVE Johann Sebastian Bach
MOVE Ludwig van Beethoven
Again, we first copy the initial values into the vector
However, as the last three rows of the output visualize, the vector now uses the move constructor to move
its elements to the new reallocated memory:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 112
This means that at the end, the vector only has to release the old memory and update its members:
• With noexcept(std::cout << name), we ask whether the call of the output expression for the name
guarantees not to throw. Here, we use noexcept as an operator that tells us whether all the corresponding
operations to perform the passed expression guarantee not to throw.
As you might imagine, with this declaration, reallocation will use the copy constructor again. The move
constructor for strings does guarantee not to throw but the output operator does not. However, the move
constructor usually does not output anything; therefore, in general when members do not throw, we can give
the guarantee not to throw for the move constructor as a whole.
The good news is that the compiler will detect noexcept guarantees for you if you do not implement the
move constructor yourself. For classes where all members guarantee not to throw in the move constructor, a
generated or defaulted move constructor will give the guarantee as a whole.
Consider the following declaration:
basics/persondefault.hpp
#include <string>
#include <iostream>
class Person {
private:
std::string name;
public:
Person(const char* n)
: name{n} {
}
This means that we print only when we copy. When the generated move constructor is used, we only see
that we do not perform a copy.
Now let us use our usual program to print out some Persons at the end:
basics/persondefault.cpp
#include "persondefault.hpp"
#include <iostream>
#include <vector>
int main()
{
std::vector<Person> coll{"Wolfgang Amadeus Mozart",
"Johann Sebastian Bach",
"Ludwig van Beethoven"};
std::cout << "capacity: " << coll.capacity() << '\n';
coll.push_back("Pjotr Iljitsch Tschaikowski");
1 For abstract base classes, you have to use a different type trait.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 115
// enable copying:
Str(const Str&) = default;
int main()
{
// create vector of 1 Million wrapped strings:
std::vector<Str> coll;
coll.resize(1000000);
We provide a class that wraps a string of significant length (to avoid the small string optimization). Note
that we have to initialize the value with parentheses because braces would interpret the 100 as the initial
character with the value 100.
In the class we mark the move constructor with NOEXCEPT, which the preprocessor can be replace with
nothing or noexcept (e.g., compile with -DNOEXCEPT=noexcept). We then measure how long it takes to
reallocate 1 million of these objects in a vector.
On almost all platforms, declaring the move constructor with noexcept makes the reallocation faster
by a factor of up to 10 (make sure a significant level of optimization is activated). That is, a reallocation
(typically forced by inserting a new element) might take about 20 instead of 200 milliseconds. This means
180 milliseconds less in which we cannot use the vector for anything. This can be a huge benefit.2
For example:
class Base {
public:
...
virtual void foo(int) noexcept;
virtual void foo(int); // ERROR: overload on different noexcept clause only
virtual void bar(int);
};
2 Note that there is a bug in Visual C++ 2015 that means that move is always used on reallocation and we do not see
a difference. This bug is fixed with Visual C++ 2017 (which might slow down the reallocation if a move constructor
does not guarantee not to throw).
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 117
virtual void foo(int) override; // ERROR: override giving up the noexcept guarantee
virtual void bar(int) noexcept; // OK (here we also guarantee not to throw)
};
However, for non-virtual functions, derived-class members can hide base-class members with a different
noexcept declaration:
class Base {
public:
...
void foo(int) noexcept;
};
class B
{
std::string s;
};
int main()
{
std::cout << std::boolalpha;
std::cout << std::is_nothrow_default_constructible<B>::value << '\n';
std::cout << std::is_nothrow_copy_constructible<B>::value << '\n';
std::cout << std::is_nothrow_move_constructible<B>::value << '\n';
std::cout << std::is_nothrow_copy_assignable<B>::value << '\n';
std::cout << std::is_nothrow_move_assignable<B>::value << '\n';
}
The output of the program is as follows:
true
false
true
false
true
The generated copy constructor and copy assignment operator might throw because copying a std::string
might throw. However, the generated default constructor, move constructor, and move assignment operator
guarantee not to throw because the default constructor, move constructor, and move assignment operator of
class std::string guarantee not to throw.4
Note that the noexcept condition is even generated when these special member functions are user-
declared with =default. Thus, we have the same effect if we declare class B as follows:
3 This also applies to the default constructor, the “pseudo” special member functions such as a defaulted operator<
or operator== (available since C++20), and even to all inherited constructors.
4 For the move assignment operator of strings, the noexcept guarantee is only given if the strings use the same or an
interchangeable allocator.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 119
class B
{
std::string s;
public:
B(const B&) = default; // noexcept condition automatically generated
B(B&&) = default; // noexcept condition automatically generated
B& operator= (const B&) = default; // noexcept condition automatically generated
B& operator= (B&&) = default; // noexcept condition automatically generated
};
When you have a defaulted special member function you can explicitly specify a different noexcept guar-
antee than the generated one. For example:
class C
{
...
public:
C(const C&) noexcept = default; // guarantees not to throw (OK since C++20)
C(C&&) noexcept(false) = default; // specifies that it might throw (OK since C++20)
...
};
Before C++20, if the generated and specified noexcept condition contradict, the defined function was
deleted.
#include <type_traits>
template<typename Base>
struct Wrapper : Base {
using Base::Base;
// implement all possibly wrapped pure virtual functions:
void print() const {}
...
};
template<typename T>
static constexpr inline bool is_nothrow_movable_v
= std::is_nothrow_move_constructible_v<Wrapper<T>>;
#endif // IS_NOTHROW_MOVABLE_HPP
You can now check even for abstract base classes whether the move constructor is noexcept. The following
program demonstrates the different behavior of the standard and the user-defined type trait:
poly/isnothrowmovable.cpp
#include "isnothrowmovable.hpp"
#include <iostream>
class Base {
std::string id;
...
public:
virtual void print() const = 0; // pure virtual function (forces abstract base class)
...
virtual ~Base() = default;
protected:
// protected copy and move semantics (also forces abstract base class):
Base(const Base&) = default;
Base(Base&&) = default;
// disable assignment operator (due to the problem of slicing):
Base& operator= (Base&&) = delete;
Base& operator= (const Base&) = delete;
};
int main()
{
std::cout << std::boolalpha;
std::cout << "std::is_nothrow_move_constructible_v<Base>: "
<< std::is_nothrow_move_constructible_v<Base> << '\n';
std::cout << "is_nothrow_movable_v<Base>: "
<< is_nothrow_movable_v<Base> << '\n';
}
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 122
• The index operator of vectors and strings is not marked with noexcept even though when used correctly
it guarantees not to throw. However, when passing an invalid index, we have undefined behavior and
implementations are allowed to do whatever they want. By not declaring them with noexcept these
implementations could throw in this case.
The second item is the guideline you should follow when implementing move semantics. We propose
that you use a conditional noexcept declaration only when implementing a move constructor, a move
assignment operator, or a swap() function. For all other functions, it is usually not worth the effort to think
about the detailed conditions and you might reveal too many implementation details.
With http://wg21.link/p0884, we added that wrapping types should also have conditional noexcept
declarations:
• If a library type has wrapping semantics to transparently provide the same behavior as the underlying
type, then the default constructor, copy constructor, and copy-assignment operator should be marked as
conditionally noexcept so that the underlying exception specification still holds.
Finally, note that by rule, any destructor is always implicitly declared as noexcept.
The consequences for your code are as follows:
• You should implement the move constructor, the move assignment operator, and a swap() function with
a (conditional) noexcept.
• For all other functions, mark them with an unconditional noexcept only if you know that they cannot
throw.
• Destructors do not need a noexcept specification.
7.5 Summary
• With noexcept, you can declare a (conditional) guarantee not to throw.
• If you implement a move constructor, move assignment operator, or swap(), declare it with a (condi-
tional) noexcept expression.
• For other functions, you might just want to mark them with an unconditional noexcept if they never
throw.
• Destructors are always declared with noexcept (even when implemented).
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 124
124
Chapter 8
Value Categories
This chapter introduces the formal terminology and rules for move semantics. We formally introduce value
categories such as lvalue, rvalue, prvalue, and xvalue and discuss their role when binding references to
objects. This allows us to also discuss details of the rule that move semantics is not automatically passed
through, as well as a very subtle behavior of decltype when it is called for expressions.
This chapter is the most complicated chapter of the book. You will probably see facts and features that are
tricky and for some people hard to believe. Come back to it later, whenever you read about value categories,
binding references to objects, and decltype again.
i = 77; // OK
77 = i; // ERROR
For this reason, each expression in a C++ program has a value category. Besides the type, the value category
is essential to decide what you can do with an expression.
However, value categories have changed over time in C++.
125
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 126
int* p1 = &x; // OK: & is fine for lvalues (object has a specified location)
int* p2 = &42; // ERROR: & is not allowed for rvalues (object has no specified location)
However, things became more complicated with ANSI-C because an x declared as const int could not
stand on the left-hand side of an assignment but could still be used in several other places where only an
lvalue could be used:
const int c = 42; // Is c an lvalue or rvalue?
expression
glvalue rvalue
For example:
class X {
};
X v;
const X c;
expression
glvalue rvalue
C createC() {
return C{ ... }; // Always creates a conceptual temporary prior to C++17.
} // In C++17, no temporary object is created at this point.
Materialization
C++17 then introduces a new term, called materialization (of an unnamed temporary), for the moment a
prvalue becomes a temporary object. Thus, a temporary materialization conversion is a (usually implicit)
prvalue-to-xvalue conversion.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 130
Any time a prvalue is used where a glvalue (lvalue or xvalue) is expected, a temporary object is created
and initialized with the prvalue (remember that prvalues are primarily “initializing values”) and the prvalue
is replaced by an xvalue that designates the temporary object. Therefore, in the example above, strictly
speaking, we have:
void f(const X& p); // accepts an expression of any value category but expects a glvalue
This rule reflects that reference or static members are not really part of an object. If you no longer need
the value of an object, this applies also to the plain data members of the object. However, the values of
members that refer to somewhere else or are static might still be used by other objects.
For example:
std::pair<std::string, std::string&> foo(); // note: member second is reference
std::vector<std::string> coll;
...
coll.push_back(foo().first); // moves because first is an xvalue here
coll.push_back(foo().second); // copies because second is an lvalue here
You need std::move() to move the member second here:
coll.push_back(std::move(foo().second)); // moves
If you have an lvalue (an object with a name), you have two options to mark a member with std::move():
• std::move(obj).member
• std::move(obj.member)
Because std::move() means “I no longer need this value here” it looks like you should mark the obj if
you no longer need the value of the object and mark member if you no longer need the value of the member.
However, the situation is a bit more complicated.
If the members are references or static, different rules apply:2 A reference or static member of an rvalue
is still an lvalue. Again, this rule reflects that the value of such a member is not really part of the object.
Saying “I no longer need the value of the object” should not imply “I no longer need the value (of a member)
that is not part of the object.”
Therefore, it makes a difference how you use std::move() if you have reference or static members:
• Using std::move() for the object has no effect:
struct S {
static std::string statString; // static member
std::string& refString; // reference member
};
S obj;
...
coll.push_back(std::move(obj).statString); // copies statString
coll.push_back(std::move(obj).refString); // copies refString
• Using std::move() for the members has the usual effect:
struct S {
static std::string statString;
std::string& refString;
};
S obj;
...
coll.push_back(std::move(obj.statString); // moves statString
coll.push_back(std::move(obj.refString); // moves refString
Whether such a move is useful is a different question. Stealing the value of a static member or referenced
member means that you modify a value outside the object you use. It might make sense, but it could also
be surprising and dangerous. Usually, a type S should better protect access to theses members.
In generic code, you might not know whether members are static or references. Therefore, using the
approach to mark the object with std::move() is less dangerous, even though it looks weird:
coll.push_back(std::move(obj).mem1); // move value, copy reference/static
coll.push_back(std::move(obj).mem2); // move value, copy reference/static
In the same way, std::forward<>(), which we introduce later, can be used to perfectly forward members
of objects. See basics/members.cpp for a complete example.
foo1(std::string{"hello"}); // OK
foo2(std::string{"hello"}); // ERROR
X v{ ... };
const X c{ ... };
Table Rules for binding references lists the formal rules for binding references to passed arguments if we
provide all the reference overloads of a function f():
void f(const X&); // read-only access
void f(X&); // OUT parameter (usually long-living object)
void f(X&&); // can steal value (object usually about to die)
void f(const X&&); // no clear semantic meaning
The numbers list the priority for overload resolution so that you can see which function is called when
multiple overloads are provided. The smaller the number, the higher the priority (priority 1 means that this
is tried first).
Note that you can only pass rvalues (prvalues, such as temporary objects without a name) or xvalues
(objects marked with std::move()) to rvalue references. That is where their name comes from.
You can usually ignore the last column of the table because const rvalue references do not make much
sense semantically, meaning that we get the following rules:
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 134
std::string s{ ... };
rvFunc(s); // ERROR: passing an lvalue to an rvalue reference
rvFunc(std::move(s)); // OK, passing an xvalue
However, note that sometimes, passing an lvalue seems to work. For example:
void rvFunc(std::string&&); // forward declaration
Compare this with the former implementation of this function not using additional parentheses. Here,
decltype of (str) yields std::string& because str is an lvalue of type std::string.
The fact that for decltype, it makes a difference when we put additional parentheses around a passed
name, will also have significant consequences when we later discuss decltype(auto).
8.7 Summary
• Any expression in a C++ program belongs to exactly one of these primary value categories:
– lvalue (roughly, for a named object or a string literal)
– prvalue (roughly, for an unnamed temporary object)
– xvalue (roughly, for an object marked with std::move())
• Whether a call or operation in C++ is valid depends on both the type and the value category.
• Rvalue references of types can only bind to rvalues (prvalues or xvalues).
• Implicit operations might change the value category of a passed argument.
• Passing an rvalue to an rvalue references binds it to an lvalue.
• Move semantics is not passed through.
• Functions and references to functions are always lvalues.
• For rvalues (temporary objects or objects marked with std::move()), plain value members have move
semantics but reference or static members have not.
• decltype can either check for the declared type of a passed name or for the type and the value category
of a passed expression.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 139
Part II
Move Semantics in Generic Code
This part of the book introduces the move semantics features that C++ provides for generic programming
(i.e., templates).
139
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 140
140
Chapter 9
Perfect Forwarding
This chapter introduces move semantics in generic code. In particular, we discuss universal references (also
called forwarding references) and (perfect) forwarding.
The following chapters then discuss tricky details of universal references and perfect forwarding and how
to deal with return values that may or may not have move semantics in generic code.
// forward declarations:
void foo(const X&); // for constant values (read-only access)
void foo(X&); // for variable values (out parameters)
void foo(X&&); // for values that are no longer used (move semantics)
We have the following rules when calling these functions:
X v;
const X c;
foo(v); // calls foo(X&)
141
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 142
Note that we do not call forward<>() once for all arguments; we call it for each argument individually.
Therefore, we have to place the ellipsis (“...”) behind the end of the forward() expression instead of
directly behind args.
However, what exactly is happening here is pretty tricky and needs a careful explanation.
X v;
const X c;
callFoo(v); // OK
callFoo(c); // OK
callFoo(X{}); // OK
callFoo(std::move(v)); // OK
callFoo(std::move(c)); // OK
In addition, they preserve the constness and value category (whether we have an rvalue or an lvalue) of the
object they are bound to:
template<typename T>
void callFoo(T&& arg); // arg is a universal/forwarding reference
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 145
X v;
const X c;
callFoo(v); // OK, arg is X&
callFoo(c); // OK, arg is const X&
callFoo(X{}); // OK, arg is X&&
callFoo(std::move(v)); // OK, arg is X&&
callFoo(std::move(c)); // OK, arg is const X&&
By rule, the type T&&, which is the type of arg, is
• An lvalue reference if we refer to an lvalue
• An rvalue reference if we refer to an rvalue
Note that a generic rvalue reference that is qualified with const (or volatile) is not a universal reference.
You can only pass rvalues:
template<typename T>
void callFoo(const T&& arg); // arg is not a universal/forwarding reference
const X c;
callFoo(c); // ERROR: c is not an rvalue
callFoo(std::move(c)); // OK, arg is const X&&
Note also that I did not talk about the type of T yet. I will explain later what type is deduced as T with
universal references.
Later, we discuss the corresponding example using lambdas.
9.2.2 std::forward<>()
Inside callFoo(), we then use the universal reference as follows:
template<typename T>
void callFoo(T&& arg) {
foo(std::forward<T>(arg)); // becomes foo(std::move(arg)) for passed rvalues
}
Like std::move(), std::forward<>() is also defined as a function template in the header file <utility>.
The expression std::forward<T>(arg) is essentially implemented as follows:
• If an rvalue of type T is passed to the function, the expression is equivalent to std::move(arg).
• If an lvalue of type T is passed to the function, the expression is equivalent to arg.
That is, std::forward<>() is a std::move() only for passed rvalues.
Just like for std::move(), the semantic meaning of std::forward<>() is I no longer need this value
here, with the additional benefit that we preserve the type (including constness) and the value category of
the object the passed universal reference binds to. You can argue that you only say conditionally that you no
longer need the value, but because you do not know whether std::forward<>() becomes a std::move(),
it would be an error to assume that the object has its value afterwards. So again, all you should assume is
that after using std::forward<>(), the object is usually valid but you do not know its value.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 146
template<typename T>
void callFoo(T&& arg) { // arg is a universal/forwarding reference
foo(std::forward<T>(arg)); // becomes foo(std::move(arg)) for passed rvalues
}
X v;
const X c;
1 We will discuss the drawbacks of having the same syntax and why no different syntax was introduced later.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 148
Coll v;
const Coll c;
std::vector<std::string> v;
const std::vector<std::string> c;
X v;
const X c{ ... };
Table Rules for binding all references lists the formal rules for binding all kinds of references to passed
arguments if we provide all possible overloads of a function f():
void f(const X&); // read-only access
void f(X&); // OUT parameter (usually long-living object)
void f(X&&); // can steal value (object usually about to die)
void f(const X&&); // contradicting semantic meaning
template<typename T>
void f(T&&); // to use perfect forwarding
template<typename T>
Call f(X&) f(const X&) f(X&&) f(const X&&) f(T&&)
f(v) 1 3 no no 2
f(c) no 1 no no 2
f(X{}) no 4 1 3 2
f(move(v)) no 4 1 3 2
f(move(c)) no 3 no 1 2
Again, the numbers list the priority for overload resolution (the smallest numbers have the highest priority)
so that you can see which function is called when multiple overloads are provided.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 150
Note that the universal reference is always the second-best option. A perfect match is always better, but
the need to convert the type (such as making it const or converting an rvalue to an lvalue) is a worse match
than just instantiating the function template for the exact type.
class X {
public:
X() = default;
X(const X&) {
std::cout << "copy constructor\n";
}
X(X&&) {
std::cout << "move constructor\n";
}
template<typename T>
X(T&&) {
std::cout << "universal constructor\n";
}
};
int main()
{
X xv;
const X xc;
However, since C++20, you can also use template parameters in lambdas:2
auto callFoo = []<typename T>(T&& arg) { // OK, universal reference since C++20
foo(std::forward<T>(arg)); // perfectly forward arg
};
9.6 Summary
• Declarations with two ampersands (name&&) can be two different things:
– If name is not a function template parameter it is an ordinary rvalue reference, binding only to rvalues.
– If name is a function template parameter it is a universal reference, binding to all value categories.
• A universal reference (called forwarding reference in the C++ standard) is a reference that can universally
refer to all objects of any type and value category. Its type is
– An lvalue reference (type&), if it binds to an lvalue
– An rvalue reference (type&&), if it binds to an rvalue
• To perfectly forward a passed argument, declare the parameter as a universal reference of a template
parameter of the function and use std::forward<>().
• std::forward<>() is a conditional std::move(). It expands to std::move() if its parameter is an
rvalue.
• It might make sense to mark objects with std::forward<>() even when calling member functions.
• Universal references are the second-best option of all overload resolutions.
• Avoid implementing generic constructors for one universal reference (or constrain them for specific
types).
Chapter 10
Tricky Details of Perfect Forwarding
After introducing (perfect) forwarding and universal/forwarding references in general in the previous chap-
ter, this chapter discusses tricky details of perfect forwarding and universal/forwarding references, such as:
• Non-forwarding universal references
• When exactly we have a universal reference
• Type deduction for universal references
The following chapters then discuss how to deal with return values that may or may not have move semantics
in generic code.
153
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 154
Forwarding Constness
This means that if we want to avoid overloading but want to have different behavior for const and non-
const arguments and support all value categories, we have to use universal references.
Consider the following example:
generic/universalconst.cpp
#include <iostream>
#include <string>
template<typename T>
void process(T&& coll)
{
iterate(coll.begin(), coll.end());
}
int main()
{
std::string v{"v"};
const std::string c{"c"};
In the program, we declare process() with a parameter as a universal reference to a collection (container,
string, etc.). As usual for universal references, we can pass strings of all possible value categories.
Inside process(), we can still have different behavior depending on whether or not the argument is
const. In this case, we call begin() and end() for the passed collection to pass them as a half-open range
to a function that iterates over all elements:
template<typename T>
void process(T&& coll)
{
iterate(coll.begin(), coll.end());
}
However, we have called different implementations of the function iterate(): one for iterators (able to
modify the elements), and one for const_iterators (not able to modify the elements). Because universal
references preserve whether a const object was passed, we call the iterate() that matches the constness
of the passed collection.
Note that we do not use (perfect) forwarding inside process(). We just want to refer universally to both
const and non-const objects. We might even use coll after iterating over all elements.
You might argue that we should know whether we modify elements when iterating over them. However,
assume that this generic function also allows us to pass the operation we call for each element. It might read
or modify the elements and for that, const correctness is important.
Note that using std::forward<>() here is at least questionable:
template<typename T>
void process(T&& coll)
{
iterate(std::forward<T>(coll).begin(), std::forward<T>(coll).end()); // ???
}
Claiming at two different locations that the same object is not longer needed is a source of trouble because
then both locations might interpret this as reason to steal the value from the object. Therefore, the location
that steals last might not use the right value. You should never use std::move() or std::forward<>()
twice for the same object (unless the object was reinitialized before the second use).
Using std::forward<>() only once here is also a source of trouble because we have no guaranteed
evaluation order for arguments of function calls:
template<typename T>
void process(T&& coll)
{
iterate(coll.begin(), std::forward<T>(coll).end()); // ???
}
In this particular example, using std::forward<>() once or twice might work because begin() and
end() do not steal/modify the value from the passed object. However, in general, it is an error unless you
know exactly how this information is used.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 156
Since C++20, constraining a universal reference to specific types is possible with the keyword requires.
However, you have to decide whether and which kind of type conversions you support:
• When the type must match (no implicit conversions allowed):
template<typename T>
requires std::is_same_v<std::remove_cvref_t<T>, std::string>
void processString(T&&) {
...
}
• When implicit conversions should be allowed:
template<typename T>
requires std::is_convertible_v<T, std::string>
void processString(T&&) {
...
}
• When even explicit conversions should be allowed:
template<typename T>
requires std::is_constructible_v<std::string, T>
void processString(T&&) {
...
}
Usually, std::is_convertible is what you want because it fits with the usual rules of function calls.
Note that std::is_convertible and std::is_constructible have the opposite order for source
and destination type of the conversion.
See generic/universaltype.cpp for a complete example with all cases.
Before C++20, you need the enable_if<> type trait instead of requires and the shortcuts with the _v
and _t suffix for type traits might not have been supported. For example, the following code supports all
types that are implicitly convertible to std::string since C++11:
template<typename T,
typename = typename std::enable_if<
std::is_convertible<T, std::string>::value
>::type>
void processString(T&& args);
To restrict to type std::string in C++11, we need:
template<typename T,
typename = typename std::enable_if<
std::is_same<typename std::decay<T>::type,
std::string
>::value
>::type>
void processString(T&& arg);
Here, the type trait std::decay<> is used to remove both referenceness and constness from type T (the type
trait std::remove_cvref<> is only provided since C++20).
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 159
In a similar way, we can constrain generic code to let universal references bind to rvalues only. However,
none of this would be necessary if we had a specific syntax for universal references. For example:
void processString(std::string&&& arg); // assume &&& declares a universal reference
We do not have this specific syntax, however, and now we have existing code with both applications of &&.
template<typename T>
void insert(T& coll, typename T::value_type&& arg)
{
coll.push_back(arg);
}
int main()
{
std::vector<std::string> coll;
...
insert(coll, std::string{"prvalue"}); // OK
...
std::string str{"lvalue"};
insert(coll, str); // ERROR: T::value_type&& is not a universal reference
insert(coll, std::move(str)); // OK
...
}
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 160
template<typename T>
class Coll {
private:
std::vector<T> values;
public:
Coll() = default;
int main()
{
Coll<std::string> coll;
...
coll.insert(std::string{"prvalue"}); // OK
std::string str{"lvalue"};
coll.insert(str); // ERROR: T&& of Coll<T> is not a universal reference
coll.insert(std::move(str)); // OK
...
}
In general, a function in a class template does not follow the rule of function templates. It is what we call a
temploid, generic code that follows the rules of ordinary functions when we instantiate the class.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 161
int main()
{
std::vector<std::string> coll;
...
insert(coll, std::string{"prvalue"}); // calls full specialization for rvalues
std::string str{"lvalue"};
insert(coll, str); // calls full specialization for lvalues
insert(coll, std::move(str)); // calls full specialization for rvalues
...
}
Note that you have to declare/define full specializations of member function templates outside the class
definition:
template<typename T>
class Cont {
...
// primary template:
template<typename U>
void insert(U&& v) { // universal reference
coll.push_back(std::forward<U>(v));
}
...
};
template<>
template<>
void Cont<std::string>::insert<>(std::string&& v)
{
coll.push_back(std::move(v));
}
template<>
template<>
void Cont<std::string>::insert<>(const std::string& v)
{
coll.push_back(v);
}
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 163
If the parameter type is an rvalue reference to a cv-unqualified template parameter and the
argument is an lvalue, the type “lvalue reference to T” is used in place of T for type deduction.
MyType v;
const MyType c;
1 Since C++17, the C++ standard uses the term forwarding reference in this definition.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 164
That means:
MyType v;
const MyType c;
MyType v;
const MyType c;
f(v); // T and arg are MyType&, forward() has no effect in this case
f(c); // T and arg are const MyType&, forward() has no effect in this case
f(MyType{}); // T is MyType, arg is MyType&&, forward() is equivalent to move()
f(std::move(v)); // T is MyType, arg is MyType&&, forward() is equivalent to move()
Note that string literals are lvalues so that we deduce T and arg for them as follows:
f("hi"); // lvalue passed, so T and arg have type const char(&)[3]
f(std::move("hi")); // xvalue passed, so T is deduced as const char[3]
// and arg has type const char(&&)[3]
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 165
Remember also that references to functions are always lvalues, Therefore, T is always deduced as an lvalue
reference if we pass a reference to a function to a universal reference:
void func(int) {
}
std::string s;
...
f<std::string>(s); // ERROR: cannot bind rvalue reference to lvalue
f<std::string&>(s); // OK, does not move and forward s
f<std::string>(std::move(s)); // OK, does move and forward s
f<std::string&&>(std::move(s)); // OK, does move and forward s
The last two calls are equivalent.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 166
These rules also apply if you use the C++20 feature to declare ordinary functions with auto&&:
void f(auto&& arg) {
g(std::forward<decltype(arg)>(arg));
}
std::vector<std::string> coll;
std::string s;
...
insert(coll, s); // ERROR: no matching function call
The problem is that both parameters can be used to deduce parameter T but the deduced types are not the
same:
• Using the parameter coll, T it is deduced as std::string.
• However, according to the special rule for universal references, parameter elem forces to deduce T as
std::string&.
Therefore, the compiler raises an ambiguity error.
There are two ways to solve this problem:
• You can use the type trait std::remove_reference<>:
template<typename T>
void insert(std::vector<std::remove_reference_t<T>>& vec, T&& elem)
{
vec.push_back(std::forward<T>(elem));
}
std::vector<std::string> coll;
std::string s;
...
insert(coll, s); // OK, with T deduced as std::string& vec now binds to coll
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 167
3 There is a rumor of another reason for coming up with a different term: some people in the C++ standards committee
may not have liked the term universal reference because it came from Scott Meyers, a guy who wrote a lot about the
outcome of the standardization of C++ without joining the committee to make things better. Yes, even in the C++
standards committee we are human, after all.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 169
10.4.2 Why && for Both Ordinary Rvalues and Universal References?
Universal/forwarding references use the same syntax as ordinary rvalue references, which is a serious source
of trouble and confusion. So why did we not introduce a specific syntax for universal references?
An alternative proposal, for example, might have been (and is sometimes discussed as a possible fix):
• Use two ampersands for ordinary rvalue references:
void foo(Coll&& arg) // arg is an ordinary rvalue reference
• Use three ampersands for universal references:
template<typename Coll>
void foo(Coll&&& arg) // arg is universal/forwarding reference
However, these three ampersands might just look too ridiculous (whenever I show this option people laugh).
Unfortunately, it would have been better to use the three ampersands because it would make code more
intuitive.
As discussed, when constraining universal references to concrete types, we would then just have to de-
clare
void processString(std::string&&& arg); // assume &&& declares a universal reference
instead of
template<typename T>
requires std::is_convertible_v<T, std::string>
void processString(T&& arg);
or even:
template<typename T,
typename =
typename std::enable_if<std::is_convertible<T, std::string>::value
>::type>
void processString(T&& args);
There is an important lesson to be learned here: it is better to have a ridiculous but clear syntax than to have
a cool but confusing mess.
10.5 Summary
• You can use universal references to bind to all const and non-const objects without losing the informa-
tion about the constness of the object.
• You can use universal references to implement special handling for passed arguments with move seman-
tics even without using std::forward<>().
• To have a universal reference of a specific type, you need concepts/requirements (since C++20) or some
template tricks (up to C++17).
• Only rvalue references of function template parameters are universal references. Rvalue references of
class template parameters, members of template parameters, and full specializations are ordinary rvalue
references, you can only bind to rvalues.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 170
• When specifying the types of universal references explicitly, they act no longer as universal references.
Use Type& to be able to pass lvalues then.
• The C++ standards committee introduced forwarding reference as a “better” term for universal reference.
Unfortunately, the term forwarding reference restricts the purpose of universal references to a common
specific use case and creates the unnecessary confusion of having two terms for the same thing. Therefore,
use universal/forwarding reference to avoid even more confusion.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 171
Chapter 11
Perfect Passing with auto&&
After discussing the usual case of move semantics in generic code, perfect forwarding a parameter, we
now have to talk about dealing with return values perfectly. In this chapter, we discuss how to forward
return values perfectly to somewhere else. For this purpose, we introduce auto&& as another way to declare
a universal reference (again also called forwarding reference). However, we also discuss applications of
auto&& that have nothing to do with forwarding values.
The next chapter then discusses how to perfectly return values.
171
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 172
}
void process(std::string&) {
std::cout << "process(std::string&)\n";
}
void process(std::string&&) {
std::cout << "process(std::string&&)\n";
}
int main()
{
process(computeConstLRef("tmp")); // calls process(const std::string&)
std::string str{"lvalue"};
process(computeLRef(str)); // calls process(std::string&)
}
the value category of the return value is an lvalue, which means that the return value is perfectly forwarded
with the best match for a non-const lvalue reference:
std::string str{"lvalue"};
process(computeLRef(str)); // calls process(std::string&)
• If compute() returns an rvalue reference:
std::string&& computeRRef(std::string&& str) {
return std::move(str);
}
the value category of the return value is an xvalue, which means that the return value is perfectly for-
warded with the best match for an rvalue reference, allowing process() to steal its value:
process(computeRRef("tmp")); // calls process(std::string&&)
process(computeRRef(std::move(str))); // calls process(std::string&&)
• If compute() returns a new temporary object by value:
std::string computeValue(const std::string& str) {
return str;
}
the value category of the return value is a prvalue, which means that the return value is perfectly forwarded
with the best match for an rvalue reference, allowing process() to also steal its value:
process(computeValue("tmp")); // calls process(std::string&&)
Note that by returning a const value:
const std::string computeConstValue(const std::string& str) {
return str;
}
or a const rvalue reference:
const std::string&& computeConstRRef(std::string&& str) {
return std::move(str);
}
you have again disabled move semantics:
process(computeConstValue("tmp")); // calls process(const std::string&)
process(computeConstRRef("tmp")); // calls process(const std::string&)
If we had a declaration for const&&, that overload would be taken.
Therefore: do not mark values returned by value with const and do not mark returned non-const rvalue
references with const.
The answer is that again, you need a universal/forwarding reference, which, however, is not declared as a
parameter. For this purpose, auto&& was introduced.
Instead of:
// pass return value of compute() to process():
process(compute(t)); // OK, uses perfect forwarding of returned value
you can also implement the following:
// pass return value of compute() to process() with some delay:
auto&& ret = compute(t); // initialize a universal reference with the return value
...
process(std::forward<decltype(ret)>(ret)); // OK, uses perfect forwarding of returned value
Or, when using brace initialization:
// pass return value of compute() to process() with some delay:
auto&& ret{compute(t)}; // initialize a universal reference with the return value
...
process(std::forward<decltype(ret)>(ret)); // OK, uses perfect forwarding of returned value
See generic/perfectautorefref.cpp for a complete example with all cases.
• If the passed type decltype(ref) is an rvalue reference, which it is if ref was initialized with a returned
plain value or rvalue reference, the expression casts ref to an rvalue reference, which is the effect of
std::move(ref).
Therefore, if we initialize the universal reference with the return value of a function:
auto&& ret{compute(t)}; // initialize a universal reference with the return value
the expression
process(std::forward<decltype(ret)>(ret)); // perfectly forward the return value
expands to
process(std::move(ret));
if and only if compute() returned an rvalue (such as a temporary object or an rvalue reference).
}
The reason we declare range as a universal reference is that we want to be able to bind it to every range so
that we can use it twice (once to ask for its beginning, and once to ask for its end) without creating a copy
or losing the information about whether or not the range is const.
The loop should work for:
• A non-const lvalue:
std::vector<int> coll;
...
for (int& i : coll) {
i *= 2;
}
• A const lvalue:
const std::vector<int> coll{0, 8, 15};
...
for (int i : coll) {
...
}
• A prvalue:
for (int i : std::vector<int>{0, 8, 15}) {
...
}
Note that there is no other way to declare range for all these cases:
• With auto, we would create a copy of the range (which takes time and disables modification of the
elements).
• With auto&, we would disable initialization of the range with a temporary prvalue.
• With const auto&, we would lose any non-constness of the range we iterate over.
However, note that there is a significant problem with the range-based for loop the way it is specified now.
Code such as the following:
std::vector<std::string> createStrings();
...
for (char c : createStrings().at(0)) { // fatal runtime error
...
}
becomes:
std::vector<std::string> createStrings();
...
auto&& range = createStrings().at(0); // OOPS: universal reference to reference
auto pos = range.begin(); // return value of createStrings() destroyed here
auto end = range.end();
for ( ; pos != end; ++pos ) {
char c = *pos;
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 178
...
}
All valid references extend the lifetime of the value they bind to. This also applies to rvalue references.
However, we do not bind to the return value of createString() (that would work fine); we bind to a
reference to the return type of createStrings() returned by at(). This means that we extend the lifetime
of the reference but not of the return value of createString(). Therefore, the loop iterates over strings
that were already destroyed.
}
The problem is that the elements in a std::vector<bool> are not objects of type bool; they are single bits.
The way this is implemented is that for std::vector<bool> the type of a reference to an element is not a
reference to the element type. The implementation of class std::vector<bool> is a partial specialization
of the implementation of the primary template std::vector<T> where references to elements are objects
of a proxy class that you can use like a reference:
namespace std {
template< ... >
class vector<bool, ... > {
public:
...
class reference {
...
};
...
};
}
A value of this class std::vector<bool>::reference is returned when dereferencing an iterator. There-
fore, in the expanded code of the range-based for loop, the statement
auto& elem = *pos;
tries to bind a non-const lvalue reference to a temporary object (prvalue), which is not allowed.
However, there is a solution to the problem: use a universal reference when calling the range-based for
loop instead:
template<typename Coll, typename T>
void assign(Coll& coll, const T& value) {
for (auto&& elem : coll) { // note: universal reference support proxy element types
elem = value;
}
}
Because a universal reference can bind to any object of any value category (even to a prvalue), using this
generic code with vector<bool> now compiles:
std::vector<bool> collB{false, true, false};
...
assign(collB, true); // OK (universal reference used to bind to an element)
We have thus found another reason to use non-forwarding universal references: they bind to reference types
that are not implemented as references. Or, more generally: they allow us to bind to non-const objects
provided as proxy types to manipulate objects.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 180
std::string s{"BLM"};
callFoo(s); // OK, arg is std::string&
callFoo(std::move(s)); // OK, arg is std::string&&
Note that since C++20, you can avoid using decltype(arg) by declaring the lambda with template para-
meters.
The following generic lambda uses this to perfectly forwards a variadic number of arguments:
[] (auto&&... args) {
...
foo(std::forward<decltype(args)>(args)...);
};
Remember that lambdas are just an easy way to define function objects (objects with operator() defined
to allow their use as functions). The definition above expands to a compiler-defined class (closure type) with
universal references defined as template parameters:
class NameDefinedByCompiler {
...
public:
template<typename... Args>
auto operator() (Args&&... args) const {
...
foo(std::forward<decltype(args)>(args)...);
}
};
Again, note that a generic rvalue reference that is qualified with const (or volatile) is not a universal
reference. This also applies to lambdas. If a parameter is declared with const auto&&, you can only pass
rvalues:
auto callFoo = [](const auto&& arg) { // arg is not a universal reference
...
};
11.6 Summary
• Do not return by value with const (otherwise you disable move semantics for return values).
• Do not mark returned non-const rvalue references with const.
• auto&& can be used to declare a universal reference that is not a parameter. Just like any universal
reference, it can refer to all objects of any type and value category and its type is
– An lvalue reference (Type&) if it binds to an lvalue
– An rvalue reference (Type&&) if it binds to an rvalue
• Use std::forward<decltype(ref )>(ref ) to perfectly forward a universal reference ref that is declared
with auto&&.
• You can use universal references to refer to both const and non-const objects and use them multiple
times without losing the information about their constness.
• You can use universal references to bind to references that are proxy types.
• Consider using auto&& in generic code that iterates over elements of a collection to modify them. That
way, the code works for references that are proxy types.
• Using auto&& when declaring parameters of a lambda (or function, since C++20) is a shortcut for declar-
ing parameters that are universal references in a function template.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 182
182
Chapter 12
Perfect Returning with
decltype(auto)
After discussing perfect forwarding a parameter and perfectly passing through a returned value, we now have
to talk about returning values perfectly. This chapter introduces the new placeholder type decltype(auto).
We will also see surprising consequences such as the strong recommendation not to use unnecessary paren-
theses in return statements.
183
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 184
• Any return type that is a reference (auto&, const auto&, and auto&&) will return a reference to a local
object if foo() returns a temporary object by value. Fortunately, compilers can issue a warning when
they detect such a bug.
That is, we need a way to say:
• Return by value if we got/have a value
• Return by reference if we got/have a reference
but still keep both the type and the value category of what we return.
C++14 introduced a new placeholder type for this purpose: decltype(auto).
template<typename T>
decltype(auto) callFoo(T&& arg) // since C++14
{
return foo(std::forward<T>(arg));
}
With this declaration, callFoo() returns by value, if foo() returns by value, and callFoo() returns by
reference if foo() returns by reference. In all cases, both the type and the value category are retained.
12.2 decltype(auto)
Just like the other placeholder type auto, decltype(auto) is a placeholder type that lets the compiler
deduce the type at initialization time. However, in this case, the type is deduced according to the rules of
decltype:
• If you initialize it with or return a plain name, the return type is the type of the object with that name.
• If you initialize it with or return an expression, the return type is the type and value category of the
evaluated expression as follows with the following encoding:
– For a prvalue, it just yields its value type: type
– For an lvalue, it yields its type as an lvalue reference: type&
– For an xvalue, it yields its type as an rvalue reference: type&&
For example:
std::string s = "hello";
std::string& r = s;
to the caller of call(). Therefore, we can call both functions that return by value and functions that return
by reference. For example:
generic/call.cpp
#include "call.hpp"
#include <iostream>
#include <string>
std::string nextString()
{
return "Let's dance";
}
int main()
{
auto s = call(nextString); // call() returns temporary object
When calling
auto s = call(nextString);
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 187
the function call() calls the function nextString() without any arguments and returns its return value
perfectly to initialize s.
When calling
auto&& ref = call(returnArg, std::move(s));
the function call() calls the function returnArg() with s marked with std::move(). returnArg()
returns back the passed argument as an rvalue reference, which then call() perfectly returns to the caller
to initialize ref. str still has its value and ref refers to it:
s: Let’s dance
ref: Let’s dance
With
auto str = std::move(ref);
we move the value of both s and ref to str, which means we get the following situation:
s:
ref:
str: Let’s dance
When calling
call(print, std::cout, ref) << '\n';
the function call() calls the function print() with std::cout and ref as perfectly forwarded arguments.
print() returns the passed stream back as an lvalue reference, which is then returned perfectly to the caller
of call().
In this function, the type of ret is just the perfectly deduced type of f(). By using if constexpr (a
compile-time if available since C++17), we use the two ways of decltype(auto) and decltype to de-
duce a type as follows:
• If ret is declared as an rvalue reference, decltype(auto) uses the expression std::move(ret), which
is an xvalue, to deduce an rvalue reference. So we move the value returned by f() to the caller of this
function.
• If ret is declared as a plain value or lvalue reference, decltype(auto) uses the type of the name ret,
which is then also a value type or an lvalue reference type.
Other solutions do not always work:
• The following would do the right thing even before C++20, but we have a performance issue:
decltype(auto) call( ... )
{
decltype(auto) ret{f( ... )};
...
return static_cast<decltype(ret)>(ret); // perfect return but unnecessary copy
}
The fact that we always use a static_cast<> might disable move semantics and copy elision. For plain
values, it is like having an unnecessary std::move() in the return statement.
• Simply returning ret would not always work:
decltype(auto) call( ... )
{
decltype(auto) ret{f( ... )};
...
return ret; // may be an ERROR
}
The return type of call() would be correct. However, if f() returns an rvalue reference, we cannot
return the lvalue ret because a non-const revalue reference does not bind to an lvalue.
• Using auto&& to declare ret would not work because you would then always return by reference:
decltype(auto) call( ... )
{
auto&& ret{f( ... )};
...
return ret; // fatal runtime error: returns a reference to a local object
}
Do never put additional parentheses around a returned name when using decltype(auto):
decltype(auto) call( ... )
{
decltype(auto) ret{f( ... )};
...
if constexpr (std::is_rvalue_reference_v<decltype(ret)>) {
return std::move(ret); // move value returned by f() to the caller
}
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 189
else {
return (ret); // FATAL RUNTIME ERROR: always returns an lvalue reference
}
}
In this case, the return type decltype(auto) switches to the rules of expressions and always deduces an
lvalue reference because ret is an lvalue (an object with a name),
If you are used to putting parentheses around names and expressions in return statements, stop doing that.
It was never necessary, but now it might even be an error when using decltype(auto).
12.3 Summary
• decltype(auto) is a placeholder type for deducing a value type from a value and a reference type from
a reference.
• Use decltype(auto) to perfectly return a value in a generic function.
• In a return statement, never put parentheses around the return value/expression as a whole.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 191
Part III
Move Semantics in the C++
Standard Library
After introducing all features of move semantics, this part of the book describes several applications of these
features in the C++ standard library.
This gives you a good overview of the use of move semantics in practice.
191
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 192
192
Chapter 13
Move-Only Types
One major application of move semantics is move-only types. These are types where objects represent a
value or the “own” a resource for which copying does not make any sense. However, it should still be
possible to pass the value or the ownership around (e.g., passing it as an argument, returning it, or storing it
in a container).
Examples of move-only types in the C++ standard library are:
• IOStreams
• Threads
• Unique pointers
In all these examples, an object represents a resource (an opened stream, a running thread, or an allocated
object). The object “owns” the resource in the sense that the destructor of the object will release the resource
(close the stream, end or wait for the end of the thread, release the allocated object).
You can pass the ownership around but copying of the resource is not supported. Copying might not make
sense for semantic reasons (what is a copy of an opened file, what is a copy of a running thread?) or for
technical reasons (if we have two owners of the same resource, we have to synchronize the access or deal
with potential consequences).
Thus, move-only types simplify the management of unique resources. The type system is used to disable
copying but we can still pass these resources around. There is always only one location at a time where the
the resource is stored/managed. However, we can still refer to these resources with pointers or references.
193
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 194
For move-only types, a couple of operations that are usually valid are not possible:
• You cannot use std::initializer_lists because they are usually passed by value, which requires
copying of the elements:
std::vector<MoveOnly> coll{ MoveOnly{}, ... }; // ERROR
• You can only iterate by reference over all move-only elements of a container:
std::vector<MoveOnly> coll;
...
for (const auto& elem : coll) { // OK
...
}
...
for (auto elem : coll) { // ERROR: can’t copy move-only elements
...
}
See lib/moveonly.cpp for a complete program with all example statements from this section.
MoveOnly mo;
sink(mo); // ERROR: can’t pass lvalue mo to arg
sink(std::move(mo)); // OK, might move mo to something inside sink()
There was a discussion between Scott Meyers and Herb Sutter (two of the leading authors of the C++
community) about how a sink function for a move-only type should be declared. Herb’s position was to take
the argument by value, Scott’s position was to take it by rvalue reference.
As far as I know, they later agreed that it would be better to take the argument by rvalue reference.
However, the real answer is that for your code, this should not matter. The usual rule for std::move()
should also apply here: if you pass a move-only object with std::move(), you might or might not lose its
value. If it is important for you to give up ownership (because you want to ensure that the file is closed,
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 196
the thread has stopped, or the associated resource was released), ensure this directly after the call with a
corresponding statement. For example:
MoveOnly mo;
13.2 Summary
• Move-only types allow us to move “owned” resources around without being able to copy them. The
copying special member functions are deleted.
• You cannot use move-only types in std::initializer_lists.
• You cannot iterate by value over collections of move-only types.
• If you pass a move-only object to a sink function and want to ensure that you have lost ownership (file
closed, memory freed, etc.), explicitly release the resource directly afterwards.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 198
198
Chapter 14
Moving Algorithms and Iterators
The C++ standard library has special support for moving elements while iterating over them. For this
purpose, special algorithms and special iterators (move iterators) were introduced with C++11. This chapter
discusses how to use them.
template<typename T>
199
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 200
// move assign the first three values inside coll2 to the end
// - not changing any size
std::move_backward(coll2.begin(), coll2.begin()+3, // source range
coll2.end()); // destination range
For example, given you have a sequence of the following integer values:
1 2 3 4 5 4 3 2 1
calling the algorithm std::remove() with the value 2 to remove all elements with the value 2 modifies the
sequence as follows:
1 3 4 5 4 3 1 2 1
All elements that do not have the value 2 are moved to the front and as the new end (the position behind the
“last” element), the position of the 2 is returned.
If possible, these algorithms move. That is, they leave elements where the value was moved-away in a
moved-from state. In this case, if the elements were strings, the last 2 would have been untouched, but the
last 1 would be moved forward before the 2 so that the last element gets a moved-from state.
Therefore, these algorithms can also leave elements in a moved-from state. In fact, the following algo-
rithms can create moved-from states:
• std::remove() and std::remove_if()
• std::unique()
Let us look at a full example. Consider a class where we can see whether an element has a moved-from
state:
lib/email.hpp
#include <iostream>
#include <cassert>
#include <string>
...
int main()
{
std::vector<Email> coll{ "tomdomain.de", "jillcompany.com",
"sarahdomain.de", "hanacompany.com" };
val.substr(val.size()-3) == ".de";
});
template<typename T>
void print(const std::string& name, const T& coll)
{
std::cout << name << " (" << coll.size() << " elems): ";
for (const auto& elem : coll) {
std::cout << " \"" << elem << "\"";
}
std::cout << "\n";
}
int main()
{
std::vector<std::string> coll{"don't", "vote", "for", "liars"};
print("coll", coll);
process(std::move(elem));
}
});
print("coll", coll);
}
In this program, we move all elements that have a size of 4 to a helper function process():
// move away only the elements processed:
std::for_each(std::make_move_iterator(coll.begin()),
std::make_move_iterator(coll.end()),
[] (auto&& elem) {
if (elem.size() != 4) {
process(std::move(elem));
}
});
To do this, we take the element marked, which the move iterators marked with std::move(), by universal
reference and pass it with std::move() to process(). Because process() takes the argument by value,
the value is essentially moved away.
As a result, all elements that do not have a size of 4 become moved-from objects in the container coll.
Therefore, the output of this program is as follows (? signaling that we do not know the value):
coll (4 elems): "don’t" "vote" "for" "liars"
- process(don’t)
- process(for)
- process(liars)
coll (4 elems): "?" "vote" "?" "?"
Usually, the last line is as follows:
coll (4 elems): "" "vote" "" ""
As you can see, a helper function std::make_move_iterator() is used so that you do not have to specify
the element type when declaring the iterator. Since C++17, class template argument deduction (CTAD)
enables simply declaring the type std::move_iterator directly without the need to specify the element
type:
std::for_each(std::move_iterator{coll.begin()},
std::move_iterator{coll.end()},
[] (auto&& elem) {
if (elem.size() != 4) {
process(std::move(elem));
}
});
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 207
template<typename T>
void print(const std::string& name, const T& coll)
{
std::cout << name << " (" << coll.size() << " elems): ";
for (const auto& elem : coll) {
std::cout << " \"" << elem << "\"";
}
std::cout << "\n";
}
int main()
{
std::list<std::string> src{"don't", "vote", "for", "liars"};
print("src", src);
print("vec", vec);
}
The program has the following output: (? signaling that we do not know the value):
src (4 elems): "?" "?" "?" "?"
vec (4 elems): "don’t" "vote" "for" "liars"
Note again that the number of elements in the source container did not change. We moved all elements to the
initialized new container. Therefore, the elements in the source range are in a moved-from state afterwards
and we do not know their values.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 208
14.4 Summary
• The C++ standard library provides special algorithms to move multiple elements, called std::move()
(same name as the std::move() to mark a single object as movable) and std::move_backward().
They leave elements in a moved-from state.
• Removing algorithms can leave elements in a moved-from state.
• Move iterators allow us to use move semantics when iterating over elements. You can use these iterators
in algorithms or other places such as constructors where ranges are used to initialize/set values. However,
ensure that the iterators use each element only once.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 209
Chapter 15
Move Semantics in Types of the C++
Standard Library
This chapter discusses the most important or remarkable applications of move semantics in the C++ standard
library.
It might help you to better understand basic types such as strings and containers and give you some
insights into tricky use of move semantics.
209
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 210
210 Chapter 15: Move Semantics in Types of the C++ Standard Library
int main()
{
std::string s0;
std::string s1{"short"};
std::string s2{"a string with an extraordinarily long value"};
std::cout << "- s0 capa: " << s0.capacity() << " ('" << s0 << "')\n";
std::cout << " s1 capa: " << s1.capacity() << " ('" << s1 << "')\n";
std::cout << " s2 capa: " << s2.capacity() << " ('" << s2 << "')\n";
std::string s3{std::move(s1)};
std::string s4{std::move(s2)};
std::cout << "- s1 capa: " << s1.capacity() << " ('" << s1 << "')\n";
std::cout << " s2 capa: " << s2.capacity() << " ('" << s2 << "')\n";
std::cout << " s3 capa: " << s3.capacity() << " ('" << s3 << "')\n";
std::cout << " s4 capa: " << s4.capacity() << " ('" << s4 << "')\n";
s4 = std::move(s5);
std::cout << "- s4 capa: " << s4.capacity() << " ('" << s4 << "')\n";
std::cout << " s5 capa: " << s5.capacity() << " ('" << s5 << "')\n";
}
In this program, you can first see that usually even empty strings have capacity for some characters due to
the small string optimization (SSO), which usually reserves 15 or 22 bytes for the value in the string itself.
Beyond the size for SSO, the string allocates the memory on the heap, which has at least size necessary to
store the value. Therefore, after
std::string s0;
std::string s1{"short"};
std::string s2{"a string with an extraordinarily long value"};
we might get something like one of the following outputs:
• Platform A:
- s0 capa: 15 (’’)
s1 capa: 15 (’short’)
s2 capa: 43 (’a string with an extraordinarily long value’)
• Platform B:
- s0 capa: 22 (’’)
s1 capa: 22 (’short’)
s2 capa: 47 (’a string with an extraordinarily long value’)
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 211
Here, both platforms seem to support SSO (for up to 15 or 22 chars) and the second platforms allocates
memory for 47 chars when only memory for 43 chars is needed.
On all platforms, a moved-from string is usually empty. This applies even when the value is not stored in
externally allocated memory (so that we have to copy all characters). After:
std::string s3{std::move(s1)};
std::string s4{std::move(s2)};
we usually get something like:
• Platform A:
- s1 capa: 15 (’’)
s2 capa: 15 (’’)
s3 capa: 15 (’short’)
s4 capa: 43 (’a string with an extraordinarily long value’)
• Platform B:
- s1 capa: 22 (’’)
s2 capa: 22 (’’)
s3 capa: 22 (’short’)
s4 capa: 47 (’a string with an extraordinarily long value’)
However, note that there is no guarantee that s1 becomes empty. As usual, the C++ standard library guar-
antees only that a moved-from string is in a valid but unspecified state, which means that s1 could still have
the value "short" or even any other value.
Move assigning a different string value might shrink the capacity. The last two steps of the example
program essentially perform:
std::string s4{"a string with an extraordinarily long value"};
std::string s5{"quite a reasonable value"};
s4 = std::move(s5);
You can find the following outputs in practice:
• A platform swapping memory:
- s4 capa: 43 (’a string with an extraordinarily long value’)
s5 capa: 24 (’quite a reasonable value’)
- s4 capa: 24 (’quite a reasonable value’)
s5 capa: 43 (’’)
• A platform moving memory (after freeing the old memory):
- s4 capa: 47 (’a string with an extraordinarily long value’)
s5 capa: 31 (’quite a reasonable value’)
- s4 capa: 31 (’quite a reasonable value’)
s5 capa: 22 (’’)
As you can see, the capacity of s4 usually shrinks, sometimes to the capacity of the destination, sometimes
to the minimum capacity for empty strings. However, neither is guaranteed.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 212
212 Chapter 15: Move Semantics in Types of the C++ Standard Library
std::list<std::string> v;
...
v = createAndInsert(); // move assignment
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 213
However, note that we have additional requirements and guarantees that apply to the move constructor and
move assignment operator of all containers except std::array<>. These requirements and guarantees
essentially mean that moved-from containers are usually empty.
214 Chapter 15: Move Semantics in Types of the C++ Standard Library
• Move element by element from the source cont1 to the destination cont2 and destroy all remaining
elements not overwritten in the destination.
Both ways require linear complexity, which is therefore specified. However, that with this definition, just
swapping the contents of the source and the destination is not allowed.
However, since C++17, all containers guarantee not to throw when the memory is interchangeable.1 For
example:
template<typename T, typename Allocator = allocator<T>>
class list {
public:
...
list& operator=(list&&)
noexcept(allocator_traits<Allocator>::is_always_equal::value);
...
};
This noexcept guarantee for the assignment operator rules out the second option to implement the move
assignment as an element-by-element move. This is because in general, move operations might throw. Only
the implementation that destroys old elements does not throw.2 Therefore, when the memory is interchange-
able, we have to use the first option to implement the move assignment operator.
In summary, effectively we have the following guarantees for containers after using them as source in a
move assignment:
• If the memory is interchangeable (which is especially true if the default standard allocator is used), it
is essentially required that the moved-from container is empty. This applies to all containers except
std::array<>.
• Otherwise, the moved-from container is in a valid but unspecified state.
However, note that after a move assignment to itself, a container always has an unspecified but valid state.
Insert Functions
For example, vectors support move semantics by having two different implementations of push_back():
template<typename T, typename Allocator = allocator<T>>
class vector {
public:
...
1 See http://wg21.link/n4258.
2 Destructors can technically throw but this breaks basic guarantees of the C++ standard library. That is why all destruc-
tors are noexcept by default, meaning that leaving a destructor with an exception will usually std::terminate()
the program.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 215
Emplace Functions
Since C++11, containers also provide emplace functions (such as emplace_back() for vectors). Instead
of passing a single argument of the element type (or convertible to the element type), you can pass multiple
arguments to initialize a new element directly in the container. That way you save a copy or move.
Note that even then, containers can benefit from move semantics by supporting move semantics for the
initial arguments of constructors.
Functions like emplace_back() use perfect forwarding to avoid creating copies of the passed arguments.
For example, for std::vector<>, the emplace_back() member function is defined as follows:
template<typename T, typename Allocator = allocator<T>>
class vector {
public:
...
// insert a new element with perfectly forwarded arguments:
template<typename... Args>
constexpr T& emplace_back(Args&&... args) {
...
// call the constructor with the perfectly forwarded arguments:
place_element_in_memory(T(std::forward<Args>(args)...));
...
}
...
};
Internally, the vector initializes the new element with the perfectly forwarded arguments.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 216
216 Chapter 15: Move Semantics in Types of the C++ Standard Library
// constructors:
constexpr pair();
constexpr pair(const T1& x, const T2& y);
template<typename U, typename V> constexpr pair(U&& x, V&& y);
pair(const pair&) = default;
pair(pair&&) = default;
template<typename U, typename V> constexpr pair(const pair<U, V>& p);
template<typename U, typename V> constexpr pair(pair<U, V>&& p);
template<typename... Args1, typename... Args2>
pair(piecewise_construct_t, tuple<Args1...> first_args,
tuple<Args2...> second_args);
// assignments:
pair& operator=(const pair& p);
pair& operator=(pair&& p) noexcept( ... );
template<typename U, typename V> pair& operator=(const pair<U, V>& p);
template<typename U, typename V> pair& operator=(pair<U, V>&& p);
// other:
void swap(pair& p) noexcept( ... );
};
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 218
218 Chapter 15: Move Semantics in Types of the C++ Standard Library
As you can see, the class supports move semantics. We have a defaulted move constructor and an imple-
mented move assignment operator (which has a corresponding noexcept condition not to throw if both
member types guarantee not to throw):3
template<typename T1, typename T2>
struct pair {
...
pair(pair&&) = default;
...
pair& operator=(pair&& p) noexcept( ... );
...
};
Therefore, the following code:
std::pair<std::string, std::string> p1{"some value", "some other value"};
auto p2{p1};
auto p3{std::move(p1)};
std::cout << "p1: " << p1.first << '/' << p1.second << '\n';
std::cout << "p2: " << p2.first << '/' << p2.second << '\n';
std::cout << "p3: " << p3.first << '/' << p3.second << '\n';
has the following output:
p1: /
p2: some value/some other value
p3: some value/some other value
However, type std::pair<> also supports perfect forwarding by dealing with universal/forwarding refer-
ences:
template<typename T1, typename T2>
struct pair {
...
template<typename U, typename V> constexpr pair(U&& x, V&& y);
...
};
Therefore, we can use move semantics when initializing a pair. For example:
int val = 42;
std::string s1{"value of s1"};
std::pair<std::string, std::string> p4{std::to_string(val), std::move(s1)};
3 The assignment operator cannot be defaulted because of special handling that is necessary when member types are
references.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 219
std::cout << "p5: " << p5.first << '/' << p5.second << '\n';
std::cout << "p6: " << p6.first << '/' << p6.second << '\n';
has the following output:
p5: answer/
p6: answer/is 42
When initializing p6, we convert the first member of p5 (declared as const char*) to a std::string,
while using move semantics when using the second member of p5 to initialize the second member of p6.
Finally, note that std::pair<> supports members that have reference types. In this case, special rules
apply when using std::move() for these members. See basics/members.cpp for a complete example.
std::make_pair()
std::pair<> comes with a convenience function template std::make_pair<>() for creating pairs with-
out having to specify the types of the members:
auto p{std::make_pair(42, "hello")}; // creates std::pair<int, const char*>
std::make_pair<>() is a good example to demonstrate one additional thing you have to take into account
when using move semantics with rvalue and universal/forwarding references. Its declaration has changed
through the different versions of the C++ standard:
• In the first C++ standard, C++98, make_pair<>() was declared inside namespace std using call-by-
reference to avoid unnecessary copying:
template<typename T1, typename T2>
pair<T1,T2> make_pair (const T1& a, const T2& b)
{
return pair<T1,T2>(a,b);
}
This, however, almost immediately caused significant problems when using pairs of string literals or raw
arrays. For example, when "hello" was passed as the second argument, the type of the corresponding
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 220
220 Chapter 15: Move Semantics in Types of the C++ Standard Library
parameter b became a reference to an array of const chars (const char(&)[6]). Therefore, type
char[6] was deduced as type T2 and used as type of the second member. However, initializing an array
member with an array is not possible because you cannot copy arrays.
In this case the decayed type should be used as member type, which is the type you get when you
would pass the argument by value (const char* for string literals).
• As a consequence, with C++03, the function definition was changed to use call-by-value:4
template<typename T1, typename T2>
pair<T1,T2> make_pair (T1 a, T2 b)
{
return pair<T1,T2>(a,b);
}
As you can read in the rationale for the issue resolution, “it appeared that this was a much smaller change
to the standard than the other two suggestions, and any efficiency concerns were more than offset by the
advantages of the solution.”
• With C++11, make_pair() had to support move semantics, which meant that the arguments had to
become universal/forwarding references. Again, we have the problem that for references the type of the
arguments does not decay. Therefore, the definition changed as follows:
template<typename T1, typename T2>
constexpr pair<typename decay<T1>::type, typename decay<T2>::type>
make_pair (T1&& a, T2&& b)
{
return pair<typename decay<T1>::type,
typename decay<T2>::type>(forward<T1>(a),
forward<T2>(b));
}
which, since C++14, can be written as follows:
template<typename T1, typename T2>
constexpr pair<decay_t<T1>, decay_t<T2>>
make_pair (T1&& a, T2&& b)
{
return pair<decay_t<T1>, decay_t<T2>>(forward<T1>(a), forward<T2>(b));
}
The real implementation is even more complex since C++11: to support std::ref() and std::cref(),
the function also unwraps instances of std::reference_wrapper<>.
The C++ standard library perfectly forwards passed arguments in many places in a similar way, often
combined with using std::decay<>.
222 Chapter 15: Move Semantics in Types of the C++ Standard Library
std::vector<std::string> coll;
std::optional<std::string> optStr;
...
coll.push_back(std::move(optStr).value()); // OK, moves from member into coll
Note that class std::optional<> is one of the rare places in the C++ standard library where const rvalue
references are used. The reason is that std::optional<> is a wrapper type that wants to ensure that the
operations do the right thing even when const objects are marked with std::move() and the contained
type provides special behavior for const rvalue references.
224 Chapter 15: Move Semantics in Types of the C++ Standard Library
std::unique_ptr<std::string> source()
{
static long id{0};
int main()
{
std::unique_ptr<std::string> p;
for (int i = 0; i < 10; ++i) {
p = source(); // p gets ownership of the returned object
// (previously returned object of source() is deleted)
std::cout << *p << '\n';
...
}
} // last-owned object of p is deleted
As usual for moved-from types, to pass ownership to a sink function you can only pass it with std::move():
std::vector<std::unique_ptr<std::string>> coll;
std::unique_ptr<std::string> up;
...
coll.push_back(up); // ERROR: copying disabled
coll.push_back(std::move(up); // OK, moves ownership into new element of coll
If you pass a unique pointer to a potential sink function that takes the argument by reference, you do not
know whether the ownership was moved. Whether the ownership was moved depends on the implementation
of the function. In that case, you can double-check the state with operator bool():
std::unique_ptr<std::string> up;
...
sink(std::move(up)); // might move ownership to sink()
if (up) { // does it still have the ownership?
...
}
Alternatively, you might ensure that the ownership is gone (resource released):
std::unique_ptr<std::string> up;
...
sink(std::move(up)); // might move ownership to sink()
up.reset(); // ensure ownership is gone (resource deleted)
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 225
void storeData(std::ofstream fstrm) // takes ownership of file (but this might change)
{
fstrm << 42 << '\n';
} // closes the file
int main()
{
auto outFile{openToWrite("iostream.tmp")}; // open file
storeData(std::move(outFile)); // store data
226 Chapter 15: Move Semantics in Types of the C++ Standard Library
Here, the function openToWrite() opens and returns an output file stream:
std::ofstream openToWrite(const std::string& name)
{
std::ofstream file(name); // open a file to write to
...
return file; // return ownership (open file)
}
We use the return value to initialize outFile and pass it to storeData():
auto outFile{openToWrite("iostream.tmp")}; // open file
storeData(std::move(outFile)); // store data
Because storeData() takes the argument by value, it takes the ownership of the open file. Therefore, at
the end of storeData(), the file is closed:
void storeData(std::ofstream fstrm) // takes ownership of file (but this might change)
{
...
} // closes the file
However, storeData() might also take the argument by reference, which means that it does not necessarily
take the ownership. In that case, you might want to double-check the state of the passed argument outFile
afterwards:
// better ensure that the file is closed:
if (!outFile.is_closed()) {
outFile.close();
}
Calling outFile.close() is usually enough but would set the failbit of the file stream if it was already
closed.
Finally, you can use std::getline() to parse the first line from a temporary stream:
std::string multiLineString, firstLine;
...
std::getline(std::stringstream{multiLineString}, // read from temporary string stream
firstLine);
int main()
{
std::vector<std::thread> threads; // better std::jthread since C++20
5 std::jthread fixes some flaws of class std::thread by becoming a RAII type (no further need to call join() or
detach() for the thread before the destructor is called) and supporting a cooperative mechanism to signal cancellation
of the thread. It is always better to use std::jthread instead of std::thread.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 228
228 Chapter 15: Move Semantics in Types of the C++ Standard Library
threads.push_back(std::thread{doThat, std::move(someArg)});
...
void getValue(std::promise<std::string> p)
{
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 229
try {
std::string ret{"vote"};
...
// store result:
p.set_value_at_thread_exit(std::move(ret));
}
catch(...) {
// store exception:
p.set_exception_at_thread_exit(std::current_exception());
}
}
int main()
{
std::vector<std::future<std::string>> results;
// create promise and future to deal with outcome of the thread started:
std::promise<std::string> p;
std::future<std::string> f{p.get_future()};
results.push_back(std::move(f));
230 Chapter 15: Move Semantics in Types of the C++ Standard Library
The thread takes it as an argument for the passed callable getValue(), which takes the passed promise by
value:
void getValue(std::promise<std::string> p)
{
...
}
This way, getValue() receives ownership of the promise and destroys this source of the communication
mechanism at the end of the thread.
getValue() could also take the promise by reference because the thread started holds the moved value
of the promise until it ends.
15.7 Summary
We have learned a lot of individual lessons from the types discussed in this chapter but let me summarize
here a couple of the more general aspects of using move semantics with types of the C++ standard library.
• Moved-from standard strings are usually empty but that is not guaranteed.
• Moved-from standard containers (except std::array<>) are usually empty if no special allocator is
used. For vectors, this is (indirectly) guaranteed; for the other containers, it is indirectly guaranteed that
all elements are moved away or destroyed and inserting new members would make no sense.
• Move assignments can change the capacity of strings and vectors.
• To support decaying when passing values to universal/forwarding references (often necessary to deduce
the same type for string literals of different length), use the type trait std::decay<>.
• Generic wrapper types should use reference qualifiers to overload member functions that access the
wrapped objects. This might even mean using overloads for const rvalue references.
• Avoid copying of shared pointers (e.g., by passing them by value).
• Use std::jthread (available since C++20) instead of std::thread.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 231
Glossary
This glossary provides a short definition of the most important non-trivial technical terms used in this book.
C
CPP file
A file in which definitions of variables and non-inline functions are located. Most of the executable (as
opposed to declarative) code of a program is normally placed in CPP files. They are named CPP files
because they usually have the suffix .cpp. However, for historical reasons, the suffix also might be .C, .c,
.cc, or .cxx. See also header file and translation unit.
F
forwarding reference
One of two terms for rvalue references of the form T&& where T is a deducible template parameter. Special
rules that differ from ordinary rvalue references apply (see perfect forwarding). The term was introduced by
C++17 as a replacement for universal reference because the primary use of such a reference is to forward
objects. However, note that it does not automatically forward. That is, the term does not describe what it is
but rather what it is typically used for.
full specialization
An alternative definition for a (primary) template that no longer depends on any template parameter.
G
glvalue
A category of expressions that produce a location for a stored value (generalized localizable value). A
glvalue can be an lvalue or an xvalue. The chapter about value categories describes details.
231
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 232
232 Glossary
H
header file
A file meant to become part of a translation unit through a #include directive. Such files often contain
declarations of variables and functions that are referred to from more than one translation unit, as well as
definitions of types, inline functions, templates, constants, and macros. They usually have a suffix such as
.hpp, .h, .H, .hh, or .hxx. They are also called include files. See also CPP file and translation unit.
I
include file
See header file.
incomplete type
A class, struct, or unscoped enumeration type that is declared but not defined, an array of unknown size,
void (optionally with const and/or volatile), or an array of an incomplete element type.
L
lvalue
A category of expressions that produce a location for a stored value that is not assumed to be movable (i.e.,
glvalues that are not xvalues). Typical examples are expressions that denote named objects, string literals,
and (references to) functions. The chapter about value categories describes details.
N
named return value optimization (NRVO)
A feature that allows us to optimize away the creation of a return value from a named object in a return
statement. If we already have a local object that we return in a function by value, the compiler can use the
object directly as a return value instead of copying or moving it.
Note that the named return value optimization differs from the return value optimization (RVO), which is
the optimization for returning an object created on the fly in the return statement. The named return value
optimization is optional in all versions of C++. If the compiler does not generate corresponding code, move
semantics is used to create the return value.
P
prvalue
A category of expressions that perform initializations. Prvalues can be assumed to designate pure mathe-
matical value such as 1 or true and temporaries (especially values returned by value). Any rvalue before
C++11 is a prvalue in C++11. The chapter about value categories describes details.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 233
Glossary 233
R
return value optimization (RVO)
A feature that allows us to optimize away the creation of a return value from a temporary object that is
created in the return statement. When we create the return value in the return statement on the fly and
return it by value, the compiler can use the temporary object directly as a return value instead of copying or
moving it.
Note that the return value optimization differs from the named return value optimization (NRVO), which
is the optimization for returning a named local object. The return value optimization was optional before
C++17 but is mandatory since C++17 (named return value optimization is still optional).
rvalue
A category of expressions that are not lvalues. An rvalue can be a prvalue (such as a temporary object
without name) or an xvalue (e.g., an lvalue marked with std::move()). What was called an rvalue before
C++11 is called a prvalue since C++11. The chapter about value categories describes details.
S
small/short string optimization (SSO)
An approach to save allocating memory for short strings by always reserving memory for a certain number
of characters. A typical value in standard library implementations is to always reserve 16 or 24 bytes
of memory so that the string can have 15 or 23 characters (plus 1 byte for the null terminator) without
allocating memory. This makes all string objects larger but usually saves a lot of running time because in
practice, strings are often shorter than 16 or 24 characters and allocating memory on the heap is quite an
expensive operation.
T
translation unit
A CPP file with all the header files and standard library headers it includes using #include directives,
minus the program text that is excluded by conditional compilation directives such as #if. For simplicity, it
can also be thought of as the result of preprocessing a CPP file. See CPP file and header file.
U
universal reference
One of two terms for rvalue references of the form T&& where T is a deducible template parameter. Special
rules that differ from ordinary rvalue references apply (see perfect forwarding). The term was coined by
Scott Meyers as a common term for both lvalue reference and rvalue reference. Because “universal” was,
well, too universal, the C++17 standard introduced the term forwarding reference instead.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 234
234 Glossary
V
value category
A classification of expressions. The traditional value categories lvalues and rvalues were inherited from C.
C++11 introduced alternative categories: glvalues (generalized lvalues), whose evaluation identifies stored
objects, and prvalues (pure rvalues), whose evaluation initializes objects. Additional categories subdivide
glvalues into lvalues (localizable values) and xvalues (eXpiring values). In addition, since C++11, rvalues
serve as a general category for both xvalues and prvalues (before C++11, rvalues were what prvalues are
since C++11). The chapter about value categories describes details.
variadic template
A template with a template parameter that represents an arbitrary number of types or values.
X
xvalue
A category of expressions that produce a location for a stored object that can be assumed to be no longer
needed. A typical example is an lvalue marked with std::move(). The chapter about value categories
describes details.
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 235
Index
235
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 236
236 Index E
Index F 237
238 Index M
Index N 239
not best match 150 check for move constructor of base class
moved-from members 97 120
moved-from objects 28 move constructor 110
guarantees 89 usage 122
invalid 89 violation 112
requirements 89 NRVO 232
reuse 29
moved-from state 89 O
member 202
ofstream 225
move-only types 196
temporary 226
move iterator 204 optional<> 221
move-only ostream 225
moved-from state 196 temporary 226
threads 227 overloading
move-only type 193 by reference and by value 134
declaration 194 on reference qualifiers 79
in containers 194 references 30
initializer_list<> 194 overload resolution
move-only types with rvalue references 133
sink function 195 with universal references 149
move semantics
and destructor 93 P
and emplace_back() 214, 215
disable 53, 104 packaged task 228
for containers 212 pair<> 217
for getters 79 make_pair() 219
for IOStreams 225 parameter
for smart pointers 222 by value 31
for std::array<> 216 perfect forwarding 141
for std::shared_ptr<> 222 rvalue reference 26
for std::unique_ptr<> 223 partially formed 90
pass-by-reference 30
for strings 209
value categories 133
initialization 70
pass-by-value 31
performance on initialization 70
versus pass-by-reference 73, 195
passing
N perfect 171
pass through move semantics 42
name perfect forwarding 141
and decltype 136 auto&& 175
named objects 59 details 153
named return value optimization (NRVO) 232 emplace_back() 215
noexcept 107 lambdas 151, 180
and inheritance 116 std::pair<> 218
and special member functions 117 variadic template 143
Josuttis: C++ Move Semantics 2020/12/19 12:33 page 240
240 Index S
Index T 241
242 Index X