# Standard Library Features
# Values Categories and Move Semantics
# Lvalues
- Can appear on the left side of built-in asignment ooperator
- Can take its address
- Can bind to Lvalue references
Here are some examples:
The whole code compile as normal, no errors.
#include <iostream>
int main()
{
int a;
// Lvalues can appear on the left side of
// the built-in assignment operator:
a = 0;
// The address of lvalues can be taken:
int* a_ptr = &a;
// Lvalues can bind to lvalue references:
int& a_ref = a;
// Examples of lvalues:
// (*) Name of a variable:
a;
// (*) "Member of object" expression:
struct foo { int _b; };
foo f;
f._b;
// (*) Function call returning lvalue reference:
int& bar();
&bar();
bar() = 5;
std::cout << ("All good");
}
int& bar() { static int i = 0; return i; }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Rvalues
- Rvalues can NOT appear on the left side of the built-in assignment operator
- The address of rvalues can NOT be taken
- Rvalues do NOT bind to lvalue references
The meaning of a Rvalue:
- An rvalue represents a temporary value object that has no identity. We can assume that an rvalue is ready to give away ownership of its resources.
Here are some examples, as you can see some errors occur:
Here is the code:
int bar() { return 5; }
int main()
{
// Rvalues can NOT appear on the left side of
// the built-in assignment operator:
5 = 0;
bar() = 0;
// The address of rvalues can NOT be taken:
&5;
&bar();
// Rvalues do NOT bind to lvalue references:
int& lv_ref0 = 5;
int& lv_ref1 = bar();
// Rvalues bind to rvalue references, introduced
// in C++11:
int&& rv_ref0 = 5;
int&& rv_ref1 = bar();
// Examples of rvalues:
// (*) Numeric literals:
5;
10.33f;
// (*) Built-in arithmetic expressions:
5 + 10 * 3;
// (*) Function calls returning non-references:
bar();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# References
Here we are going to overload functions to select the value for either copy or transfer resources. If the value is a rvalue we transfer if it is a lvalue we copy.
In this case, when invoking foo(a)
or foo(5)
, the compiler is going to select the first overload for the lvalues and the second overload for the rvalue.
At bar(a)
and bar(5)
, it can take a lvalue
as well as a rvalue
. this is because it takes a const int&
.
#include <iostream>
void foo(int&)
{
std::cout << "non-const lvalue ref\n";
}
void foo(int&&)
{
std::cout << "non-const rvalue ref\n";
}
void bar(const int&)
{
std::cout << "const lvalue ref\n";
}
int main()
{
int a = 0;
foo(a);
foo(5);
bar(a);
bar(5);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Output:
non-const lvalue ref
non-const rvalue ref
const lvalue ref
const lvalue ref
2
3
4
# Summary
- C++ expressions have a value category; they can either be lvalues or rvalues
- Lvalues can appear on the left side of built-in assignment, rvalues cannot
- Non-const lvalues references can only bind to lvalues
- Non-const rvalues references can only bind to rvalues
# Move Semantics
- Transferring ownership of resources instead of copying them
- Relying on rvalue references
- Colloquially express the idea of "moving" objects, although
std::move()
is a static_cast of a rvalue reference.
- The rvalue expressions represent temporariness and lack of "identity"
- The
std:move
is just a cast to an rvalue that expresses the "intention" of moving - It is unsafe to use objects after they have been "moved from"
- Moving when returning is unnecessary and sometimes detrimental
In C++03 we were forced to make a copy,
std::vector<data> v0;
std::vector<data> v1 = v0;
2
From C++11 and above we use move semantics
std::vector<data> v0;
std::vector<data> v1 = std::move(v0);
2
# The meaning of Rvalues:
A rvalue represents a temporary object that has no identity. We can assume that a rvalue is ready to give away ownership of its resources.
#include <vector>
std::vector<int> get_a_vector();
int main()
{
std::vector<int> v0{1, 2, 3, 4, 5};
// The lvalue `v0` is an `std::vector` which
// owns a dynamically-allocated buffer.
auto v1 = v0;
// If we instantiate a new vector `v1` and
// initialize it with the lvalue `v0`, we are
// forced to perform a copy of `v0`'s internal
// buffer.
// An alternative consists of releasing the
// ownership of the source vector's buffer
// and giving it away to the destination.
// When can that be done safely? Only when
// the source is an rvalue, as it doesn't
// have an "identity" and it is about to
// "expire".
auto v2 = get_a_vector();
// In this case, `get_a_vector()` is an rvalue
// expression - we know that the returned
// vector has no "identity" and is about to
// "expire" - therefore we can give away the
// ownership of its internal buffer instead
// of performing a copy of all its elements.
// Why?
// http://en.cppreference.com/w/cpp/container/vector/vector
}
std::vector<int> get_a_vector()
{
return {1, 2, 3, 4, 5};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Move Constructor
- Move constructor. Constructs the container with the contents of other using move semantics. Allocator is obtained by move-construction from the allocator belonging to other. After the move, other is guaranteed to be empty().
vector( vector&& other );
//(since C++11)
2
# std::move
- It's literally a cast to a rvalue reference
- Doesn't actually "move" anything, but express "intent to move"
- Can be used to turn a lvalue into a rvalue
- It is generally unsafe to use an object after it has been moved
- Moving when returning is unnecessary and sometimes detrimental
#include <type_traits>
#include <utility>
#include <vector>
int main()
{
// Let's now analyze a different scenario:
// we have an lvalue `v0` that we want to
// "move" to a destination vector.
std::vector<int> v0{1, 2, 3, 4, 5};
// We, as developers, know that `v0` won't
// be used anymore in the current scope and
// that an unnecessary copy can be avoided.
// How can we inform the compiler of our
// intentions?
auto v1 = std::move(v0);
// `std::move` does precisely that: it casts
// an existing lvalue expression to an rvalue,
// so that the implementation of `std::vector`
// can take advantage of the fact that `v0`
// is about to "expire".
// How does `std::move` work? It literally
// is only a `static_cast` to an rvalue
// reference.
// http://en.cppreference.com/w/cpp/utility/move
auto v2 = static_cast<std::vector<int>&&>(v1);
// In the line above, we perform the same the
// same operation `std::move` does, albeit in
// a more verbose and less expressive manner.
// Note that using `v0` or `v1` here would
// lead to undefined behavior.
}
// One very important thing to understand is that
// `std::move` doesn't actually "move" anything:
// it simply casts an expression to an rvalue.
// Think of `std::move` as a way of telling the
// compiler that "we would like to move this
// particular object if possible, and we swear
// that we're not going to use it again later".
void noop_example()
{
std::vector<int> v0{1, 2, 3, 4, 5};
std::move(v0); // No-op.
v0.size(); // Perfectly safe.
}
// Another thing to be aware of is that using
// `std::move` when returning from a function is
// unnecessary and sometimes detrimental, as it
// prevents RVO ("return value optimization").
std::vector<int> return_example()
{
std::vector<int> v0{1, 2, 3, 4, 5};
// Wrong:
// return std::move(v0);
// Correct:
return v0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Return value of move
It's a static_cast
to a rvalue reference of T
of the original value (t)
static_cast<typename std::remove_reference<T>::type&&>(t)
# Practical Uses of std move
- Many classes in the Standard Library are "move aware"
- Some classes represent "unique ownership" and are move-only
Avoiding Unnecessary Performance Hits with std::move
:
- Moving objects into containers
- Moving containers instead of copying
- Accepting rvalues references as function arguments
- "sink arguments" are objects that are going to be retained/consumed
- Providing an optimal interface for "sink arguments" requires 2^N overloads for N arguments (all possible combinations of const& and &&)
- The "pass-by-value and move" idiom is easy to implement and maintain, and achieves close-to-optimal performance.
- Many classes in the Standard Library are move-aware or move-only
- Using
std::move
can provide performance benefits and allows us to work with move-only classes - Taking a "sink argument", use the "pass-by-value and move" idiom
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include <mutex>
struct foo
{
};
void pair_and_tuple()
{
// General-purpose utility classes such as
// `std::pair` and `std::tuple` are move-aware.
std::pair<foo, int> p;
auto p_copy = p;
auto p_move = std::move(p);
// http://en.cppreference.com/w/cpp/utility/pair/pair
std::tuple<foo, int, char> t;
auto t_copy = t;
auto t_move = std::move(t);
// http://en.cppreference.com/w/cpp/utility/tuple/tuple
// If the items contained in them have valid
// move operations, they will be properly used
// when moving the pair/tuple.
}
void containers()
{
// Every container in C++ is move-aware in one
// way or another. The entire container can be
// moved to a destination, or items can be moved
// inside the containers.
std::vector<foo> v;
auto v_copy = v;
auto v_move = std::move(v);
// Moves the temporary inside the vector.
v.push_back(foo{});
foo f;
// Copies `f` inside the vector.
v.push_back(f);
// Moves `f` inside the vector.
v.push_back(std::move(f));
// http://en.cppreference.com/w/cpp/container/vector/push_back
}
void move_only()
{
// Some classes provided by the Standard Library
// can only be moved. These classes represent
// "unique ownership" over a resource: copying
// them would be nonsensical.
std::thread t{[] { std::cout << "hello!"; }};
// auto t_copy = t; // Does not compile.
auto t_move = std::move(t);
// http://en.cppreference.com/w/cpp/thread/thread/thread
// Similarly, `unique_lock` and `unique_ptr`
// are move-only.
{
std::mutex m;
std::unique_lock<std::mutex> ul{m};
// auto ul_copy = ul; // Does not compile.
auto ul_move = std::move(ul);
}
{
std::unique_ptr<int> up = std::make_unique<int>(1);
// auto up_copy = up; // Does not compile.
auto up_move = std::move(up);
}
// Using `std::move` is required when working
// with these classes, as we need to express
// the intent of "transferring ownership" to
// the compiler.
}
int main()
{
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
cpp reference - mutex (see unique_lock)
thread
The copy constructor which takes a const lvalue reference (const thread&
) has been deleted since C++11, using the new delete
keyword from C++11. Meaning that this is not allowed to be invoked.
thread( const thread& ) = delete;
(4) (since C++11)
2
# Avoiding Unnecessary Performance Hits with std::move:
- Moving objects into containers
- Moving containers instead of copying
- Accepting rvalues references as function arguments
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>
std::string read_large_file(int index);
std::vector<int> get_multiples(int x);
void consume_multiples(std::map<int, std::vector<int>> m);
void moving_into_containers()
{
// As briefly mentioned earlier, moving items
// into containers can be a huge performance
// win.
// Consider the case where we read large
// files into an `std::string`, then put
// the string inside an `std::vector`:
std::vector<std::string> files;
for (int i = 0; i < 10; ++i)
{
std::string s = read_large_file(i);
// ...do some processing on `s`...
files.push_back(s); // Wrong
}
// What is wrong with the code above?
// How can we improve it?
}
void moving_containers_instead_of_copying()
{
// `std::string` is a container of characters.
// The example we looked at above applies to
// any other container, but sometimes it is
// less obvious.
// It is fairly common to have an `std::map`
// where the value is another container.
std::map<int, std::vector<int>> multiples_of;
for (int i = 0; i < 100; ++i)
{
auto i_multiples = get_multiples(i);
multiples_of[i] = i_multiples;
}
consume_multiples(multiples_of);
// What is wrong with the code above?
// How can we improve it?
}
void bar(const std::vector<int>& v);
void bar(std::vector<int>&& v);
int main()
{
std::vector<int> v;
bar(v);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# Fixing void moving_into_containers()
Not using std::move here forces the compiler to copy the string inside the vector, and if the string is big enough it can have a very big impact in your performance.
Another small optimization is reserving the memory of the vector first hand (line 12). This will ensure that when we push_back inside the vector no reallocations will occur because we already have enough space for 10 elements.
void moving_into_containers()
{
// As briefly mentioned earlier, moving items
// into containers can be a huge performance
// win.
// Consider the case where we read large
// files into an `std::string`, then put
// the string inside an `std::vector`:
std::vector<std::string> files;
files.reserve(10);
for (int i = 0; i < 10; ++i)
{
std::string s = read_large_file(i);
// ...do some processing on `s`...
files.push_back(std::move(s));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Fixing void moving_containers_instead_of_copying()
- If we have a
sdt::vector<int>
inauto i_multiples
and we assign it to values ofmultiples_of[i]
we are forcing the compiler to perform a copy, becausei_multiples
is a lvalue.
So we need to use sdt::move and gain a lot of performance, like this:
auto i_multiples = get_multiples(i);
multiples_of[i] = std::move(i_multiples);
2
Alternatively, if the code is short enough and readability doesn't suffer, we can simply use get_multiples(i)
and get rid of the temporary local variable, so that get_multiples(i)
will be a rvalue, like so:
void moving_containers_instead_of_copying()
{
// `std::string` is a container of characters.
// The example we looked at above applies to
// any other container, but sometimes it is
// less obvious.
// It is fairly common to have an `std::map`
// where the value is another container.
std::map<int, std::vector<int>> multiples_of;
for (int i = 0; i < 100; ++i)
{
multiples_of[i] = get_multiples(i);
}
consume_multiples(std::move(multiples_of));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
We can also avoid a copy using std::move
here consume_multiples(std::move(multiples_of));
, since we are passing the map by value, so we are assuming that the function will somehow consume it and store it somewhere, we are not passing by const reference we can avoid a copy.
Since we are taking it by value, we can choose whether we want to copy or move the source into the destination function argument.
void consume_multiples(std::map<int, std::vector<int>> m);
Finally, here we can use bar(v)
or bar(std::move(v))
, because of the overload.
Providing this flexibility is good for the user because they will always get optimal performance from a rvalue or lvalue.
This is why the pass by value idiom
exists.
void bar(const std::vector<int>& v);
void bar(std::vector<int>&& v);
int main()
{
std::vector<int> v;
bar(v); // right
bar(std::move(v)); // also right
}
2
3
4
5
6
7
8
9
# pass by value idiom
Sink argument
The sink argument will be retained inside the instance of the class, in this case name is a sink argument.
#include <iostream>
#include <string>
#include <utility>
// Before C++11, it was common to take
// "sink arguments" as `const&` and copy.
struct person
{
std::string _name;
person(const std::string& name) : _name{name}
{
}
void set_name(const std::string& name)
{
_name = name;
}
};
int main()
{
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
One possible solution is to support move operations for sink arguments by providing multiple overloads per constructor/member function:
#include <iostream>
#include <string>
#include <utility>
// Since C++11, we can support move operations
// for "sink arguments" to prevent unnecessary
// copies.
// Here's a version of `person` with two overloads
// per constructor/member function.
struct person
{
std::string _name;
person(const std::string& name) : _name{name}
{
}
person(std::string&& name) : _name{std::move(name)}
{
}
void set_name(const std::string& name)
{
_name = name;
}
void set_name(std::string&& name)
{
_name = std::move(name);
}
};
// This is optimal for the users of `person`:
// * If they pass an lvalue, it will be copied
// * If they pass an rvalue, it will be moved
// This quickly gets out of hand, as we need
// to write 2^N overloads for N arguments!
int main()
{
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
WARNING
This quickly gets out of hand, as we need to write 2^N overloads for N arguments!
That's when pass-by-value and move
idiom comes to our rescue
Here is another implementation of the person struct, much cleaner and has no code repetition.
#include <iostream>
#include <string>
#include <utility>
// Fortunately, the "pass-by-value and move"
// idiom comes to our rescue.
struct person
{
std::string _name;
person(std::string name) : _name{std::move(name)}
{
}
void set_name(std::string name)
{
_name = std::move(name);
}
};
// By taking "sink arguments" by value and
// then unconditionally moving them, we can
// achieve close-to-optimal behavior:
// * Lvalue => 1 copy + 1 move
// * Rvalue => 2 moves
// Since moves are cheap, the extra move is
// worth not having to provide 2^N overloads.
int main()
{
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Summary
- "Sink arguments" are objects that are going to be retained/consumed
- Providing an optimal interface for "sink arguments" requires 2^N overloads for N arguments (all possible combinations of const&& and &&)
- The "pass-by-value and move" idiom is easy to implement and maintain, and achieves close-to-optimal performance
# Value Categories The Full Picture
#include <utility>
// The terms "lvalue" and "rvalue" were not
// enough when designing C++11's move semantics.
// Unconditionally moving rvalues would have
// caused potentially dangerous behavior, so
// a more fine-grained model was required.
// There now are three "leaf" value categories:
// * prvalues ("pure rvalues")
// * lvalues
// * xvalues ("expiring rvalues)
// Both xvalues and lvalues are "glvalues".
// Both xvalues and prvalues are "rvalues".
// Glvalue => "has identity".
// Rvalue => "can be moved from".
struct foo { };
foo prvalue();
foo& lvalue();
foo&& xvalue();
int main()
{
// The following expression is a prvalue,
// because `foo` has no identity and can
// be moved from.
prvalue();
// The following expression is an lvalue,
// because `foo&` has identity (refers to
// something) and cannot be moved (is an
// lvalue reference).
lvalue();
// The following expression is a xvalue,
// because `foo&&` has identity (refers to
// something) and can be moved (is an
// rvalue reference).
xvalue();
foo f;
f; // `f` is an lvalue.
std::move(f); // `std::move(f)` is an xvalue.
// An xvalue can be obtained from an lvalue,
// but not a prvalue. That's why it stands for
// "pure rvalue".
// You can think of prvalues as rvalues that
// are not references (no identity).
// http://en.cppreference.com/w/cpp/language/value_category
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
cpp reference - value_category
# History
CPL
The programming language CPL was first to introduce value categories for expressions: all CPL expressions can be evaluated in "right-hand mode", but only certain kinds of expression are meaningful in "left-hand mode". When evaluated in right-hand mode, an expression is regarded as being a rule for the computation of a value (the right-hand value, or rvalue). When evaluated in left-hand mode an expression effectively gives an address (the left-hand value, or lvalue). "Left" and "Right" here stood for "left of assignment" and "right of assignment".
C
The C programming language followed a similar taxonomy, except that the role of assignment was no longer significant: C expressions are categorized between "lvalue expressions" and others (functions and non-object values), where "lvalue" means an expression that identifies an object, a "locator value"[4].
C++98
Pre-2011 C++ followed the C model, but restored the name "rvalue" to non-lvalue expressions, made functions into lvalues, and added the rule that references can bind to lvalues, but only references to const can bind to rvalues. Several non-lvalue C expressions became lvalue expressions in C++.
C++11
With the introduction of move semantics in C++11, value categories were redefined to characterize two independent properties of expressions[5]:
has identity: it's possible to determine whether the expression refers to the same entity as another expression, such as by comparing addresses of the objects or the functions they identify (obtained directly or indirectly);
can be moved from: move constructor, move assignment operator, or another function overload that implements move semantics can bind to the expression.
In C++11, expressions that:
- have identity and cannot be moved from are called lvalue expressions;
- have identity and can be moved from are called xvalue expressions;
- do not have identity and can be moved from are called prvalue ("pure rvalue") expressions;
- do not have identity and cannot be moved from are not used[6].
- The expressions that have identity are called "glvalue expressions" (glvalue stands for "generalized lvalue"). Both lvalues and xvalues are glvalue expressions.
The expressions that can be moved from are called "rvalue expressions". Both prvalues and xvalues are rvalue expressions.
- ↑ "New" Value Terminology by Bjarne Stroustrup, 2010.
- ↑ const prvalues (only allowed for class types) and const xvalues do not bind to T&& overloads, but they bind to the const T&& overloads, which are also classified as "move constructor" and "move assignment operator" by the standard, satisfying the definition of "can be moved from" for the purpose of this classification. However, such overloads cannot modify their arguments and are not used in practice; in their absence const prvalues and const xvalues bind to const T& overloads.
C++17
In C++17, copy elision was made mandatory in some situations, and that required separation of prvalue expressions from the temporary objects initialized by them, resulting in the system we have today. Note that, in contrast with the C++11 scheme, prvalues are no longer moved from.
# Perfect Forwarding
Why are forward references useful?
- They "remember" the original value category of the passed object. This allows developers to write more generic code, avoiding repetitions and providing more flexible and optimal interfaces.
How to make types movable:
- Containers in the standard library are move-aware
- Some standard library utilities are defined in terms of move semantics
std::exchange to elegantly implement move operations for your classes
#include <utility>
// When no template argument deduction is
// happening, `&` means "lvalue reference"
// and `&&` means "rvalue reference".
void take_lvalue(int&);
void take_rvalue(int&&);
// In the context of template argument
// deduction, `&&` has a different meaning:
// "forwarding reference".
template <typename T>
void take_anything(T&&);
// In the function signature above, `T&&`
// does NOT mean rvalue reference - it
// means "forwarding reference" instead.
// A "forwarding reference" binds to
// everything and remembers the original
// value category of the passed object.
void example()
{
take_anything(0);
// `0` is a prvalue, which is an rvalue.
// Inside the body of `take_anything`:
// * `T` evaluates to `int`.
// * `T&&` evaluates to `int&&`.
int x;
take_anything(x);
// `x` is an lvalue. Inside the body of
// `take_anything`:
// * `T` evaluates to `int&`.
// * `T&&` evaluates to `int&`.
}
// There is a special case that makes `T`
// evaluate to an lvalue reference when an
// lvalue is passed. This was intentionally
// added to the Standard to allow "perfect
// forwarding".
// http://en.cppreference.com/w/cpp/language/template_argument_deduction#Deduction_from_a_function_call
// Remember that `T&&` is a forwarding reference
// only when `T` is being deduced.
template <typename T>
struct foo
{
// In this case `T` is not being deduced here,
// so `T&&` is an rvalue reference to `T`.
void not_a_forwarding_reference(T&&);
};
int main()
{
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
cpp reference - template_argument_deduction
# Example
We want to provide a generic .add
member
function that accepts an std::string
and
moves it inside _words
when possible.
#include <utility>
#include <vector>
#include <string>
// Let's say we are writing a class that
// represents a vector of words.
class dictionary
{
private:
std::vector<std::string> _words;
public:
// ???
};
// We want to provide a generic `.add` member
// function that accepts an `std::string` and
// moves it inside `_words` when possible.
int main()
{
dictionary d;
std::string s{"hello"};
d.add(s); // Should copy.
d.add(std::string{"world"}); // Should move.
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
First possible solution: Provide two overloads
#include <utility>
#include <vector>
#include <string>
class dictionary
{
private:
std::vector<std::string> _words;
public:
// We could provide two overloads:
void add(const std::string& s)
{
_words.push_back(s);
}
void add(std::string&& s)
{
_words.push_back(std::move(s));
}
};
// This works as intended, but causes code
// repetition.
int main()
{
dictionary d;
std::string s{"hello"};
d.add(s);
d.add(std::string{"world"});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Second possible solution: Use the "pass-by-value and move" idiom
This is easy to write, easy to understand but its sub-optimal. Although, It's still recommended.
#include <utility>
#include <vector>
#include <string>
class dictionary
{
private:
std::vector<std::string> _words;
public:
// We could use the "pass-by-value and move"
// idiom:
void add(std::string s)
{
_words.push_back(std::move(s));
}
};
// This works, but it is not optimal. An
// extra move is executed on every invocation
// of `add`.
int main()
{
dictionary d;
std::string s{"hello"};
d.add(s);
d.add(std::string{"world"});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Third possible solution: Perfect Forwarding
TIP
An initial underscore character, that is, an underscore in the first position of an identifier, is commonly used for private identifiers.
Like in _words
#include <utility>
#include <vector>
#include <string>
// That's where "perfect forwarding" and
// `std::forward` come into play.
class dictionary
{
private:
std::vector<std::string> _words;
public:
// If we take `s` as a forwarding reference,
// we can then "forward" it to `push_back`
// in order to achieve optimal behavior without
// code repetition.
template <typename T>
void add(T&& s)
{
_words.push_back(std::forward<T>(s));
}
};
// This works and it is now optimal. The only
// drawback is that `add` is now an unconstrained
// template, and might need constraints like
// `enable_if` to make it play nicely with
// overload resolution and produce better compiler
// errors.
int main()
{
dictionary d;
std::string s{"hello"};
d.add(s);
d.add(std::string{"world"});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
How does `std::forward` work?
#include <utility>
#include <vector>
#include <string>
// How does `std::forward` work?
void sink(int&); // (0)
void sink(int&&); // (1)
template <typename T>
void pipe(T&& x)
{
sink(std::forward<T>(x));
}
// You can think of `std::forward` as a
// "conditional move". When the passed
// expression is an lvalue, `std::forward`
// will return an lvalue reference.
// When the passed expression is an rvalue
// `std::forward` will return an rvalue
// reference.
// We need to explicitly pass the `T`
// template parameter to `std::forward`.
// Since `T` evaluates to `T&` for lvalues
// with forwarding references, it is the
// only way we can "inform" `std::forward`
// that the original value category of `x`
// was an lvalue.
// http://en.cppreference.com/w/cpp/utility/forward
int main()
{
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Smart Pointers
# What problems do they solve?
- Problems with manual dynamic memory allocation
- Why new/delete are considered harmful
- Manual memory management is error-prone and can lead it to potentially security-critical bugs
- Manual memory management makes code harder to reason about and to read
- Smart pointers solve the aforementioned issues
#include <vector>
// Before C++11, dynamic memory management in C++
// has been traditionally done through the use of
// the `new` and `delete` keywords.
struct foo { };
int main()
{
foo* f = new foo;
// The `new` keyword allocates memory for a
// `foo` instance, constructs it, and returns
// a pointer to the newly-allocated memory
// location.
delete f;
// The `delete` keyword destroys the instance
// of `foo` allocated at the address `f`, then
// reclaims the dynamically allocated memory
// for future reuse.
}
// There are some major problems with the use of
// `new`/`delete`:
//
// * They are error prone. Forgetting to use the
// `delete` keyword or using it multiple times
// can lead to "memory leaks" or "double-free"
// errors, which can harm stability or introduce
// security problems. Accessing a pointer after
// `delete` has been called is another common
// very dangerous mistake.
int memory_leak()
{
int* x = new int{42};
return *x;
}
int double_free()
{
int* x = new int{42};
delete x;
delete x; // Undefined behavior!
}
int use_after_free()
{
int* x = new int{42};
delete x;
return *x; // Undefined behavior!
}
// * They increase cognitive overhead on devs and
// people reading/reviewing the code. As soon
// as a pointer is spotted in a code base, many
// questions have to be asked: "was it allocated
// dynamically?" "Am I suppose to `delete` the
// pointer after using it?", "Who is the owner
// of the allocated memory?"... and so on.
void bar(int* x)
{
// Who owns `x`?
// Am I supposed to `delete` it?
// Can I assume that it is non-null?
}
struct abc
{
std::vector<foo*> _foos;
// Is `abc` responsible for allocating and
// cleaning up the items of `_foos`?
// Does `abc` only refer to `foo` instances?
// Should `abc` be copyable? What should the
// destructor of `abc` do?
};
// All the issues are solved by smart pointers.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# std::unique_ptr
and std::make_unique
- Every "resource" needs to be acquired and then released
- Dynamic memory, files, and threads are all examples of resources
- "Unique Ownership": there is only a single owner of a particular resource, who is responsible both for its acquisition and release
- Access to the resource is only possible through its owner
To use any smart pointer we must #include <memory>