Containers have a quirk, let’s look at it.
Noexcept
Consider a hypothetical case of a 2D Point class, possibly
used for math or graphics.
1
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
|
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
struct Point
{
int x = 0;
int y = 0;
Point() {
cout << "default ctor" << endl;
}
Point(int x_, int y_)
: x{x_}
, y{y_}
{
cout << "param ctor" << endl;
}
Point(const Point&) {
cout << "copy ctor" << endl;
}
Point& operator=(const Point&) {
cout << "copy assign" << endl;
return *this;
}
Point(Point&&) {
cout << "move ctor" << endl;
}
Point& operator=(Point&&) {
cout << "move assign" << endl;
return *this;
}
};
int main()
{
vector<Point> points;
for (int i = 0; i < 5; i++)
{
cout << "Loop " << (i + 1) << endl;
points.emplace_back(1, 2);
cout << endl;
}
}
|
If we create and fill a vector of these points as in the example code above
then we get the following output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
Loop 1
param ctor
Loop 2
param ctor
copy ctor
Loop 3
param ctor
copy ctor
copy ctor
Loop 4
param ctor
copy ctor
copy ctor
copy ctor
Loop 5
param ctor
copy ctor
copy ctor
copy ctor
copy ctor
|
We see the copy constructor is called, not the move constructor.
This could be very wasteful for larger data types like image containers.
The solution is to mark the move ctor and move assign as noexcept
.
1
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
|
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
struct Point
{
int x = 0;
int y = 0;
Point() {
cout << "default ctor" << endl;
}
Point(int x_, int y_)
: x{x_}
, y{y_}
{
cout << "param ctor" << endl;
}
Point(const Point&) {
cout << "copy ctor" << endl;
}
Point& operator=(const Point&) {
cout << "copy assign" << endl;
return *this;
}
Point(Point&&) noexcept {
cout << "move ctor" << endl;
}
Point& operator=(Point&&) noexcept {
cout << "move assign" << endl;
return *this;
}
};
int main()
{
vector<Point> points;
for (int i = 0; i < 5; i++)
{
cout << "Loop " << (i + 1) << endl;
points.emplace_back(1, 2);
cout << endl;
}
}
|
The only change here is that the move constructor and move assignment operator have been marked noexcept
.
This then gives the following output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
Loop 1
param ctor
Loop 2
param ctor
move ctor
Loop 3
param ctor
move ctor
move ctor
Loop 4
param ctor
move ctor
move ctor
move ctor
Loop 5
param ctor
move ctor
move ctor
move ctor
move ctor
|
Cover Photo
Credit: Guillermo Alvarez