Question regarding class instances

I made a small program in order to understand how class instances work at runtime.
So I have 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
#include <iostream>
#include <string>

class TestClass{
	public:
	std::string txt;
	TestClass(std::string x){ txt = x; std::cout << "Instance " << txt << std::endl;}
	TestClass(){std::cout << "Empty Instance " << std::endl;}
	~TestClass(){std::cout << "Instance Deleted " << txt << std::endl; }
	void printall(){ std::cout << txt << std::endl; }
};

int main(int args, char** argv)
{
	TestClass an(std::string("A"));
	TestClass* bn = new TestClass(std::string("B"));
	TestClass* cn = new TestClass[3];
	TestClass** dn = new TestClass*[3];

	for(int x = 0; x < 3; x++)
		cn[x] = TestClass(std::string("C"));

	for(int x = 0; x < 3; x++)
		dn[x] = new TestClass(std::string("D"));

	an.printall();
	bn->printall();

	delete bn;

	for(int x = 0; x < 3; x++)
		cn[x].printall();

	for(int x = 0; x < 3; x++)
	{
		dn[x]->printall();
		delete dn[x];
	}

	delete[] cn;
	delete[] dn;
	return 0;
}


And this code produces the following output:

Instance A
Instance B
Empty Instance 
Empty Instance 
Empty Instance 
Instance C
Instance Deleted C
Instance C
Instance Deleted C
Instance C
Instance Deleted C
Instance D
Instance D
Instance D
A
B
Instance Deleted B
C
C
C
D
Instance Deleted D
D
Instance Deleted D
D
Instance Deleted D
Instance Deleted C
Instance Deleted C
Instance Deleted C
Instance Deleted A


Now, the part I don't understand is this one:

 Instance C
Instance Deleted C
Instance C
Instance Deleted C
Instance C
Instance Deleted C


Which is produced by the following portion of the code:

1
2
	for(int x = 0; x < 3; x++)
		cn[x] = TestClass(std::string("C"));


So, it creates the 3 instances of TestClass, but then they are inmediately deleted? Why does this happen? They are still in use, in fact they are still in memory so I don't understand exactly what happens here.
 
TestClass* cn = new TestClass[3];

This creates an array of 3 TestClass objects so the default constructor (the constructor that takes no arguments) is called 3 times.


 
cn[x] = TestClass(std::string("C"));

Here TestClass(std::string("C")) creates a temporary object. This object will only exist for the duration of the full-expression.

The temporary object is then assigned to the cn[x] object. This will call the move-assignment operator which will move (similar to copy but more efficient) the temporary object's txt variable to cn[x].txt.

If you want to print something when this happens you will have to define your own move-assignment operator: TestClass& operator=(TestClass&&) (or copy-assignment operator: TestClass& operator=(const TestClass&)).

Note that the constructor and destructor is only called once for each object, when the object is created and destroyed. The temporary object is destroyed after line 21 has finished. The three objects in the cn array are destroyed when you use delete on line 40.
Last edited on
So it makes a temporary instance and copies everything from it to the original. I added the operator TestClass& and added another output on it. Now I can see what is going on.

Another question, this one regarding the operators. I might not have understood how these work. Because doing this:

TestClass& operator=(TestClass&& m){ std::cout << "Temporary instance " << m.txt << std::endl; }

In the class throws a compile error:

main.cpp:10:31: error: expected ‘,’ or ‘...’ before ‘&&’ token


So I have to use the copy-assignment operator.

TestClass& operator=(const TestClass& m){txt = m.txt; std::cout << "Temporary instance " << m.txt << std::endl;}

And I am not sure if this is a good practise, is this operator called from the "original" instance, or the "copy"? Because I noticed that if I don't add this:

txt = m.txt;

In the operator, even if the constructor is being called, it is not assigned (cn[x].txt is empty at runtime). In other words, if I do this:

TestClass& operator=(const TestClass& m){txt = m.txt; std::cout << "Temporary instance " << m.txt << std::endl;}

I get this:

Instance A
Instance B
Empty Instance 
Empty Instance 
Empty Instance 
Instance C
Temporary instance C
Instance Deleted C
Instance C
Temporary instance C
Instance Deleted C
Instance C
Temporary instance C
Instance Deleted C
Instance D
Instance D
Instance D
A
B
Instance Deleted B
C
C
C
D
Instance Deleted D
D
Instance Deleted D
D
Instance Deleted D
Instance Deleted C
Instance Deleted C
Instance Deleted C
Instance Deleted A


While if I do this:

TestClass& operator=(const TestClass& m){std::cout << "Temporary instance " << m.txt << std::endl;}

I get this:

Instance A
Instance B
Empty Instance 
Empty Instance 
Empty Instance 
Instance C
Temporary instance C
Instance Deleted C
Instance C
Temporary instance C
Instance Deleted C
Instance C
Temporary instance C
Instance Deleted C
Instance D
Instance D
Instance D
A
B
Instance Deleted B



D
Instance Deleted D
D
Instance Deleted D
D
Instance Deleted D
Instance Deleted 
Instance Deleted 
Instance Deleted 
Instance Deleted A


Being the empty lines the output related to every C instance (in TestClass* cn).
Move semantics and that && thing is something new to C++ that was added in C++11. Your compiler need to be up-to-date enough and you might also need to enable C++11 features (or a later). If you don't know about it we can just ignore it for now because it's mostly a matter of making a few things more efficient, at least in this situation.

The copy-assignment operator is called on the object that's being assigned to and the object on the right hand side is passed as an argument.

This line

 
cn[x] = TestClass(std::string("C"));

could be rewritten as

 
cn[x].operator=( TestClass(std::string("C")) );

and it will do exactly the same. I don't say you should write it this way. I just think it better shows what's going on if you are inexperienced with operators.

The default copy-assignment operator copies all member variables. If you define it yourself you will have to write the code for everything to be copied if that is what you want to happen.
the real problem is that with instance create on stack , most of the time the default constructor is called even if you did not put the (). Using the heap , it wont necessarily.

One thing you can do , is initializing instance by aggregation , or put your default constructor as private.
I think I get it. If I call the default operator, by default it copies every field while if I override it, I need to do it myself. That's one good thing to know.

Also I am using gcc 5.2.0. By passing the -std=c++11 option to the compiler, the move-assignment works. I think I understand everything now. Thanks.
Topic archived. No new replies allowed.