Return an object from a function and the copy constructor

I have written a simple program to study the copy constructor.
But the output seems strange to me.
Please help me figure it out.

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

class Counter
{
public:
	Counter();
	Counter(const Counter&);
	~Counter();
};

Counter::Counter()
{
	cout << "Constructor called\n";
}

Counter::Counter(const Counter&)
{
	cout << "Copy constructor called\n";
}

Counter::~Counter()
{
	cout << "Destructor called\n";
}

Counter copy(const Counter&);

int main()
{
	cout << "creating countOne\n";
	Counter countOne;
	cout << endl;

	cout << "creating countTwo using function copy()\n";
	Counter countTwo = copy(countOne);
	cout << "address of countTwo: " << &countTwo << endl;
	cout << endl;

	cout << "countOne and countTwo are going out of scope\n";
}

Counter copy(const Counter &copied)
{
	Counter temp = copied;
	cout << "address of temp: " << &temp << endl;
	return temp;
}


 1: creating countOne
 2: Constructor called
 3: 
 4: creating countTwo using function copy()
 5: Copy constructor called
 6: address of temp: 0x22fefe
 7: address of countTwo: 0x22fefe
 8: 
 9: countOne and countTwo are going out of scope
10: Destructor called
11: Destructor called


My questions are:

1. On (output) line 5, which object is calling the copy constructor? countTwo or temp?

2. On line 6-7, why are the address of temp and countTwo the same?

3. Shouldn't a copy of temp is made when the return statement is encountered because the function is return by value?

4. Why is there no destructor call for temp?
Your compiler probably does optimization.
Then what is the output for normal compiler?
keskiverto wrote:
Your compiler probably does optimization.


I checked the code using debugger and also checked up some books and the copy ctor part in detail. I think maybe due to the fact that he didnt overload the assignment operator, a bitwise copy is performed and so temp & countTwo share the same address

EDIT :
I used GCC 4.7.2 and output's same for me.
Last edited on
man GCC-4.4.7:
-fno-elide-constructors
The C++ standard allows an implementation to omit creating a temporary which is only
used to initialize another object of the same type. Specifying this option disables
that optimization, and forces G++ to call the copy constructor in all cases.
The illusionist mirage:
I think maybe due to the fact that he didnt overload the assignment operator


I have tried Counter countTwo(copy(countOne)); and Counter temp(copied); to avoid the assignment operator, yet the output does not change.


keskiverto:
The C++ standard allows an implementation to omit creating a temporary which is only
used to initialize another object of the same type.


I modified the function to receive a Counter object by value and return it by value.
1
2
3
4
5
Counter copy(const Counter copied)
{
	cout << "address of copied: " << &copied << endl;
	return copied;
}


The middle part of the output:
 4: creating countTwo using function copy()
 5: Copy constructor called (This is for passing countOne by value right?)
 6: address of copied: 0x22feff
 7: Copy constructor called (a copy is made this time)
 8: Destructor called (Is it for the parameter or the object just returned?)
 9: address of countTwo: 0x22fefd


The same rule of optimization does not apply for parameter. Why is that?
Last edited on
The illusionist mirage wrote:
I think maybe due to the fact that he didnt overload the assignment operator, a bitwise copy is performed and so temp & countTwo share the same address

No. At no point in the OP's code does anything invoke the assignment operator. The line:

Counter temp = copied;

will invoke the copy constructor, not the assignment operator. It is exactly equivalent to:

Counter temp(copied);
> 1. On (output) line 5, which object is calling the copy constructor? countTwo or temp?

temp in the function Copy(). Copy elision can't be applied for Counter temp = copied; because the copy constructor has an observable side effect.


> 2. On line 6-7, why are the address of temp and countTwo the same?
> 3. Shouldn't a copy of temp is made when the return statement is encountered
> because the function is return by value?

The compiler has applied RVO (compilers usually do).
an implementation may omit a copy operation resulting from a return statement, even if the copy constructor has side effects. http://en.wikipedia.org/wiki/Return_value_optimization


> 4. Why is there no destructor call for temp?

There is no destructor call of temp, RVO causes temp to be constructed in the memory for countTwo. temp and CountTwo are the same object (RVO); which is why their addresses are the same.

In summary:
a. In function Copy(), temp is copy constructed into the memory set aside for CountTwo in main()
b. The function returns without a copy being made (RVO); the object is already there in the memory for CountTwo in main()
c. Eventually when main exits, CountTwo is destroyed.


> I modified the function to receive a Counter object by value and return it by value.
> The same rule of optimization does not apply for parameter. Why is that?

Copy elision can't be applied for the object passed by value because the copy constructor has an observable side effect.

Copy elision is applied for return by value because RVO is allowed "even if the copy constructor has side effects"
Last edited on
What is the observable side effect?
The observable side effect is anything that happens as a result of actually running the code inside the copy constructor. For example, you could print hello world inside the copy constructor to see it first-hand.

With RVO:
http://coliru.stacked-crooked.com/a/82f197ede4003555 (no output)

Without RVO:
http://coliru.stacked-crooked.com/a/16f7cc882716a828
Last edited on
The parameter (copied) is returned by value. Why is not RVO performed. (Instead a copy is made at the return statement)
Last edited on
Because the function needs to be able to change the state of the parameter without changing the state of the object originally used to create the parameter. If the parameter passed to the function was a temporary, then it may use copy-elision.

http://coliru.stacked-crooked.com/a/0b765ef60b0f4ad7

Notice that between A and B, the copy has to be made, but between B and C, copy-elision is allowed and is used by the compiler.
Last edited on
I'm referring to the time when the parameter is returned, not the time it is created.
Last edited on
I'm not entirely sure, but I think it has to do with calling conventions. Best to let JLBorges answer this one.
> The same rule of optimization does not apply for parameter. Why is that?

>> The parameter (copied) is returned by value. Why is not RVO performed.
>> (Instead a copy is made at the return statement)

Because the IS (International Standard) expressly prohibits copy elision for a function parameter.

The gory details (emphasis added):
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

<other clauses elided for brevity>


Essentially the same code that you wrote, with annotations:

Equivalent to the first version:
1
2
3
4
5
6
Counter copy(const Counter &copied)
{
	Counter temp = copied;
	cout << "address of temp: " << &temp << endl;
	return temp;
}

http://coliru.stacked-crooked.com/a/10bc49ad166faf50

Equivalent to the second version:
1
2
3
4
5
Counter copy(const Counter copied)
{
	cout << "address of copied: " << &copied << endl;
	return copied;
}

http://coliru.stacked-crooked.com/a/a81b61b5b3fc3535
Last edited on
What is a move constructor?

If I delete the move constructor, copy constructor is called instead.

So if copy constructor will suffice, why and when to use move constructor?
Last edited on
Move constructor is about move semantics, which is a new feature introduced in C++11. Websearch returns meaningful hits.
Thanks all for giving detailed explanation.

I will take a look at the move constructor.
Topic archived. No new replies allowed.