anonymous38653
anonymous38653

Reputation: 403

Why did the copy constructor get called?

In the following code, I create 4 objects named p1, p2, p3, and copy, of the player class and I print their attributes using a while loop, for which the code and the output are the following. But I was expecting a different output and I do not know where I did a copy of the objects in the first 3 cases.

#include <iostream>
using namespace std;
class player{
    public:
    int xp;
    string name;
    int health;

    player():player(0,0,"none") {} 
    player(int a):player(a,0,"none") {} 
    player (int a, int b, string c):name{c},xp{a},health{b} {}
    player (player &source)
    {
        name="copied player";
        xp=-1;
        health=-1;
    }
};
int main()
{
    player p1;
    player p2(2);
    player p3(2,5,"play3");
    player copy{p2};
    player arr[4]{p1,p2,p3,copy};
    int t=4;
    while(t--)
    {
        cout<<arr[3-t].name<<endl;
        cout<<arr[3-t].xp<<endl;
        cout<<arr[3-t].health<<endl;
    }
}

For which I get the following output:

copied player
-1
-1
copied player
-1
-1
copied player
-1
-1
copied player
-1
-1

However, I was expecting:

none
0
0
none
2
0
play3
2
5
copied player
-1
-1

What do I not know?

Upvotes: 3

Views: 71

Answers (1)

Adrian Mole
Adrian Mole

Reputation: 51815

As your code stands (and as pointed out in the comments), when you initialize your arr[4] array, the compiler will copy each object in the initializer list to the target - hence the invocation of the copy constructor four times.

One way to avoid this is to use std::move(x) in the initializer list but, to do this, you will need to provide a move constructor for your player class (the default will suffice, in your case).

However, remember that, after you move from an object, the source object is no longer necessarily the same as it was and using it may be invalid. The only requirement after a move (although a class may give more guarantees) is that the object is in a state where it can safely be destructed. (Thanks to the comment from Jesper Juhl for this note!)

This code will produce the output that you were expecting:

#include <iostream>
#include <utility> // Defines std::move()
using std::string;
using std::cout; using std::endl;

class player {
public:
    int xp;
    string name;
    int health;

    player() :player(0, 0, "none") {}
    player(int a) :player(a, 0, "none") {}
    player(int a, int b, string c) :name{ c }, xp{ a }, health{ b } {}
    player(player& source) {
        name = "copied player";
        xp = -1;
        health = -1;
    }
    player(player&& p) = default; // Use the compiler-generated default move c'tor
};

int main()
{
    player p1;
    player p2(2);
    player p3(2, 5, "play3");
    player copy{ p2 };
//    player arr[4]{ p1,p2,p3,copy };
    player arr[4]{ std::move(p1), std::move(p2), std::move(p3), std::move(copy) };
    int t = 4;
    while (t--) {
        cout << arr[3 - t].name << endl;
        cout << arr[3 - t].xp << endl;
        cout << arr[3 - t].health << endl;
    }
    return 0;
}

Note: Please also read: Why is "using namespace std;" considered bad practice?.

Upvotes: 3

Related Questions