For loop behave differenly each time

I am learning c++ and try to understand the object lifetime of the move constructor and copy constructor. I am using g++ 9.2.0, c++17 in vscode. I created a struct Card holding a string & int, and a struct Deck that holds cards in vector<Card>. When executing build_deck member function loop over string and int to add Card to the vector in Deck by embrace_back, the output shows that each loop behaved differently. How can this happen?

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
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <string>
#include <vector>
#include <utility>
#include <algorithm>
using namespace std;

static const std::vector<std::string> Suits {"Club","Diamond", "Heart", "Spade" };
struct Card{
    std::string suit = "Blank";
    int value = 0;

    Card()    {puts("default constructed");} 
    Card(std::string s, int v): suit(s), value(v)   
       {std:: cout << this->suit << '\t';puts("card constructed");}
    Card(const Card& cc)                              
        {std:: cout << this->suit << '\t';puts("copy constructed:");} 
    Card(Card && mc)                                  
         {std:: cout << this->suit << '\t';puts("move constructed:");} 
    Card& operator= (const Card& cc)                  
         {std:: cout << this->suit << '\t';puts("copy assign");  suit = cc.suit; value = cc.value; return *this;} 
    Card& operator= (Card&& mc)                       
         {std:: cout << this->suit << '\t';puts("move assign");  suit = std::move(mc.suit);  value = std::move(mc.value); return *this;}  
    ~Card()                 
        {std:: cout << this->suit << '\t';puts("card destroyed");}
};



struct Deck{
    std::vector<Card> cards;
    int num_remain{0} ;

    Deck(): cards(std::vector<Card>{}),num_remain(0){puts("Deck constructed");}    
    ~Deck()                                         {puts("Deck destroyed");}
    
    void build_deck(); 
};

void Deck::build_deck(){

    puts("start builing----------------------");
    cards.clear();
    num_remain = 0;

        for (auto s : Suits){
            for (int v = 1; v <= 2; ++v){ 
                cards.emplace_back(s,v);  
                num_remain++;
                std::cout << num_remain << '\n';
                puts("==========Card Added===============");
            }
        }
    puts("finish building-----------------------");
    return;
}


int main(){
    
    Deck draw; 
    Deck discard; 
    puts("~~~two deck created~~~"); 
    draw.build_deck();
    return 0;
}

The 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
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
Deck constructed
Deck constructed
~~~two deck created~~~    
start builin
Club    card constructed  
1
==========Card Added===============
Club    card constructed  
Blank   copy constructed: 
Club    card destroyed    
2
==========Card Added===============
Diamond card constructed  
Blank   copy constructed: 
Blank   copy constructed: 
Blank   card destroyed    
Club    card destroyed    
3
==========Card Added===============
Diamond card constructed  
4
==========Card Added===============
Heart   card constructed  
Blank   copy constructed: 
Blank   copy constructed: 
Blank   copy constructed: 
Blank   copy constructed: 
Blank   card destroyed    
Blank   card destroyed    
Diamond card destroyed    
Diamond card destroyed    
5
==========Card Added===============
Heart   card constructed  
6
==========Card Added===============
Spade   card constructed  
7
==========Card Added===============
Spade   card constructed  
8
==========Card Added===============
finish building-----------------------
Deck destroyed
Deck destroyed
Blank   card destroyed    
Blank   card destroyed    
Blank   card destroyed    
Blank   card destroyed    
Heart   card destroyed    
Heart   card destroyed    
Spade   card destroyed    
Spade   card destroyed 

Last edited on
The "extra" copy constructions and destructions occur when std::vector reallocates.

When std::vector runs out of room it allocates a new buffer and copies (generally) the contents of the old into the new. As part of the process the elements in the old buffer are copied and then destroyed.

std::vector will not move from its elements unless the element type's move constructor is non-throwing. Therefore declare move constructors and move assignment operators (and swap) noexcept when possible.

You could have found this by yourself by using a debugger.
Last edited on
To each of your diagnostics, I would add say

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    Card()    {cout<< "default constructed:this" << reinterpret_cast<void*>(this)<<endl;} 
    Card(std::string s, int v): suit(s), value(v)   
       {std:: cout << this->suit << '\t' << 
        "card constructed:this" << reinterpret_cast<void*>(this)<<endl;}
    Card(const Card& cc)                              
        {std:: cout << this->suit << '\t' << 
         "copy constructed:this:"<<reinterpret_cast<void*>(this) <<
         " from:"<<reinterpret_cast<const void*>(&cc)<<endl;} 
    Card(Card && mc)                                  
         {std:: cout << this->suit << '\t';puts("move constructed:");} 
    Card& operator= (const Card& cc)                  
         {std:: cout << this->suit << '\t';puts("copy assign");  
         suit = cc.suit; value = cc.value; return *this;} 
    Card& operator= (Card&& mc)                       
         {std:: cout << this->suit << '\t';puts("move assign");  
         suit = std::move(mc.suit);  value = std::move(mc.value); return *this;}  
    ~Card()                 
        {std:: cout << this->suit << '\t' << 
         "card destroyed"<< reinterpret_cast<void*>(this)<<endl;}


It'll give you a sense of what objects exist at any point in time.
==========Card Added===============
Club	card constructed:this0x1f30860
Blank	copy constructed:this:0x1f30850 from:0x1f30830
Club	card destroyed0x1f30830
2
==========Card Added===============
Diamond	card constructed:this0x1f308d0
Blank	copy constructed:this:0x1f308b0 from:0x1f30850
Blank	copy constructed:this:0x1f308c0 from:0x1f30860
Blank	card destroyed0x1f30850
Club	card destroyed0x1f30860
Hi, mbozzi, salem,

Thanks for the reply!
I found that when I put noexcept inside the move constructor, the Cards are now moved instead of copied.
Also for clarification: in my case, during putting 4, 6,7,8th card into the Deck, the vector found that the next memory is open to use and thus no reallocation happened which is a happy accident? Is this what happened?

Finally, may I ask a follow-up question regarding this case: it seems the Card added whenever reallocation happens is the default Blank card (the four Blank Cards destroy before heart and spade). How can this happen and how to make it correctly add the right suit (club and diamond)? This problem exists even after adding noexcept to move and/or copy constructor.
Try this:

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
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
#include <iostream>
#include <string>
#include <vector>
using namespace std;

static const std::vector<std::string> Suits {"Club","Diamond", "Heart", "Spade"};

struct Card {
	std::string suit {"Blank"};
	int value {};

	Card() { puts("default constructed"); }

	Card(const std::string& s, int v) : suit(s), value(v) {
		std::cout << suit << '\t';
		puts("card constructed");
	}

	Card(const Card& cc) : suit(cc.suit), value(cc.value) {
		std::cout << suit << '\t';
		puts("copy constructed");
	}

	Card(Card&& mc) noexcept : suit(std::move(mc.suit)), value(std::move(mc.value)) {
		std::cout << suit << '\t';
		puts("move constructed");
	}

	Card& operator= (const Card& cc) {
		std::cout << suit << '\t';
		puts("copy assign");

		suit = cc.suit;
		value = cc.value;
		return *this;
	}

	Card& operator= (Card&& mc) noexcept {
		std::cout << suit << '\t';
		puts("move assign");

		suit = std::move(mc.suit);
		value = std::move(mc.value);
		return *this;
	}

	~Card() {
		std::cout << suit << '\t';
		puts("card destroyed");
	}
};

struct Deck {
	std::vector<Card> cards;
	int num_remain {0};

	Deck() { puts("Deck constructed"); }
	~Deck() { puts("Deck destroyed"); }

	void build_deck();
};

void Deck::build_deck() {

	puts("start builing----------------------");

	cards.clear();
	num_remain = 0;

	for (auto s : Suits) {
		for (int v = 1; v <= 2; ++v) {
			cards.emplace_back(s, v);
			++num_remain;
			std::cout << num_remain << '\n';
			puts("==========Card Added===============");
		}
	}

	puts("finish building-----------------------");
}

int main() {
	Deck draw;
	Deck discard;

	puts("~~~two deck created~~~");
	draw.build_deck();
}



Deck constructed
Deck constructed
~~~two deck created~~~
start builing----------------------
Club    card constructed
1
==========Card Added===============
Club    card constructed
Club    move constructed:
        card destroyed
2
==========Card Added===============
Diamond card constructed
Club    move constructed:
Club    move constructed:
        card destroyed
        card destroyed
3
==========Card Added===============
Diamond card constructed
Club    move constructed:
Club    move constructed:
Diamond move constructed:
        card destroyed
        card destroyed
        card destroyed
4
==========Card Added===============
Heart   card constructed
Club    move constructed:
Club    move constructed:
Diamond move constructed:
Diamond move constructed:
        card destroyed
        card destroyed
        card destroyed
        card destroyed
5
==========Card Added===============
Heart   card constructed
6
==========Card Added===============
Spade   card constructed
Club    move constructed:
Club    move constructed:
Diamond move constructed:
Diamond move constructed:
Heart   move constructed:
Heart   move constructed:
        card destroyed
        card destroyed
        card destroyed
        card destroyed
        card destroyed
        card destroyed
7
==========Card Added===============
Spade   card constructed
8
==========Card Added===============
finish building-----------------------
Deck destroyed
Deck destroyed
Club    card destroyed
Club    card destroyed
Diamond card destroyed
Diamond card destroyed
Heart   card destroyed
Heart   card destroyed
Spade   card destroyed
Spade   card destroyed

Hi seeplus,

Thanks for the fast reply, it worked after trying your code. However, I am still wondering why my code does not work as I intended while your code does. May I ask why using the initializer list in the copy and move constructor can solve the issue I had before?
If the initializer list is not used, the object is first default constructed, then values are assigned. The values specified in the initializer list are assigned as the object is constructed.

Consider:

1
2
3
4
int a{};
a = 7;

int b {7};


a and b both get assigned the value 7 - but a first has the value 0 assigned then the value 7. b has 7 assigned directly.
The point of @seeplus's post (I think) is that neither your copy or move constructors assign the members of the newly-constructed object. All your copy or move constructor does is print "copy" (or "move"), but no copy is actually made.

Look at his code above for an example of how to define the copy/move constructors properly.
Last edited on
Topic archived. No new replies allowed.