A copy constructor of class T is a non-template constructor whose first parameter is T&‍, const T&‍, volatile T&‍, or const volatile T&‍, and either there are no other parameters, or the rest of the parameters all have default values.

from cppreference.com

Syntax

1
2
3
4
5
6
// Typical declaration of a copy constructor.
class_name ( const class_name & )
// Forcing a copy constructor to be generated by the compiler.
class_name ( const class_name & ) = default;
// Avoiding implicit generation of the copy constructor.
class_name ( const class_name & ) = delete;

When is copy constructor called?

In C++, a Copy Constructor may be called in following cases:

  • When an object of the class is returned by value.
  • When an object of the class is passed (to a function) by value as an argument.
  • When an object is constructed based on another object of the same class.
  • When compiler generates a temporary object.

Let’s talk about some cases where you might not even realize how many copies it has done. Copy is not cheap, sometimes. In some cases, copy constructor can be avoided by Copy Elision, but it is not in the scope of this discussion.

Show me an example

How many times the copy constructor is called?

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
#include <iostream>
#include <vector>
#include <string>

using std::vector;
using std::string;
using std::cout;

class Point
{
private:
int x, y;
public:
Point() {
cout << ">> empty constructor called.\n";
}

Point(int x1, int y1) {
x = x1; y = y1;
cout << ">> Normal constructor called.\n";
}

// Copy constructor
Point(const Point& p2) {
x = p2.x; y = p2.y;
cout << ">> Copy constructor called.\n";
}
};

int main()
{
int n = 2;
std::vector<Point> points;
for (int i = 0; i < n; i++) {
cout << "=== " << i << " ===\n";
points.push_back(*(new Point(1, 2)));
}

return 0;
}

The answer is 3.

  • When i is 0, it creates a new Point object, calls normal constructor.
  • Pushes it into vector, copy once.
  • When i is 1, it creates a new Point object again, calls normal constructor.
  • Pushes again, copy again.
  • Wait, before push, vector points needs to resize (usually double the size), copy points[0] to new vector first, then push.
  • In total, 3 copy constructor calls and 2 normal calls.
1
2
3
4
5
6
7
=== 0 ===
>> Normal constructor called.
>> Copy constructor called.
=== 1 ===
>> Normal constructor called.
>> Copy constructor called.
>> Copy constructor called.

You can run this code online, https://onlinegdb.com/BkuZ6vFB7. Play with it, change n to some other value to see if you can get the right number of copy constructor calls.

Can we do better?

Yes, we can use std::vector::emplace_back() instead of std::vector::push_back().

(It) Appends a new element to the end of the container. The element is constructed through std::allocator_traits::construct, which typically uses placement-new to construct the element in-place at the location provided by the container.

from cppreference.com

Let’s change the main().

1
2
3
4
5
6
7
8
9
10
11
int main()
{
int n = 2;
std::vector<Point> points;
for (int i = 0; i < n; i++) {
cout << "=== " << i << " ===\n";
points.emplace_back(1, 2); // <- uses emplace_back.
}

return 0;
}

How many times does copy constructor get called? Only once.

  • When i is 0, emplace_back calls the normal constructor of Point, put the newly created object in vector directly. no copy.
  • When i is 1, points resizes itself, need to copy the existing element points[0], one copy.
  • Then, as the first step, emplace_back creates new object in place, no copy needed.
1
2
3
4
5
=== 0 ===
>> Normal constructor called.
=== 1 ===
>> Normal constructor called.
>> Copy constructor called.

You can also run the code online, https://onlinegdb.com/SJo_lOKSX. Change n to some other values to see if you can get the right number of copy constructor calls.

Should I always use emplace_back()?

We can not deny the fact that std::vector::emplace_back() saves unnecessary copy operations and it can be a big win for some operations on large objects. However, using emplace_back means you need to take care of the constructor arguments, which sometimes could be arbitrary. Check the example in Tip of the Week #112: emplace vs. push_back, the second example constructs a vector of over a million elements, allocating several megabytes of memory in the process, if the variable type is not as what you think, and this is dangerous.

For more expensive types, this may be a reason to use emplace_back() instead of push_back(), despite the readability and safety costs, but then again it may not. Very often the performance difference just won’t matter. As always, the rule of thumb is that you should avoid optimizations that make the code less safe or less clear, unless the performance benefit is big enough to show up in your application benchmarks.

Let’s keep this in mind.

reference