Hello again! In this video, we are going to look at copy elision.
This is a form of optimization in which the compiler misses out calls to the copy constructor.
As a very simple example, imagine we have an int x with value 7 and then we use this x to initialize another
variable, y.
And then we never actually use x again until it goes out of scope.
It is very easy for compilers to see that this variable X is not actually needed, and instead of initializing
this variable x and then copying it into y, they can directly create the variable y with the initial
value,
seven.
And this also applies to objects. If we have, for example, a string which is used to initialize another
string, and nothing else, then the compiler can skip over this copy and it will initialize the permanent
variable directly.
So this will create a single object which has the initial value, as opposed to creating two objects,
one of which is a copy of the other.
So the first one, that means it has to call the constructor for "temp", then the copy constructor for "perm".
And then when "temp" goes out of scope, it will need to call the destructor for that. With the
optimized form,
It just needs to call the constructor for "perm", and then the destructor as well.
But it was going to call the destructor in that case as well.
So we have actually saved a call to the copy constructor and a call to the destructor. And as these involve
memory operations, they take a lot of processor cycles.
So that is quite a useful optimization.
So this is copy elision: the compiler is allowed to skip over the call to the copy constructor and it
can initialize the target object directly.
The usual case where this comes up is with temporary variables and function calls; either copying temporary
variables into function arguments, or copying temporary variables into the function's return value.
Normally in C++, the compiler cannot perform optimizations which change the behaviour of the program.
That is called the "as-if" rule.
So the program must behave "as if" the optimization had not happened.
So the only thing the optimization can do is to make the program run faster.
However, with copy elision, the compiler is allowed to omit the call, even if the copy
constructor has side effects.
So in that case, it will change the behaviour of the program.
Modern compilers will always perform copy elision if they can. So if you do not want it, you have to
turn it off.
For most compilers, there -s an option, "minus f-no-elide-constructors". For visual C++,
the documentation says you have to disable all optimizations, "/Od", but that does not
work for me.
So here is an example of this. We have a class, Test.
It has a copy constructor.
This prints out a message, so this has a side effect.
It changes the state of the standard output stream object.
We define a default construction.
We actually have to define this.
And you might like to think about why that is.
The reason is that we define a copy constructor. And if we define any constructor, including a copy
constructor, the compiler will not synthesize a default constructor. And we need a constructor, so
we have to implement one.
And for good measure, we are going to print out a message as well, so we can see what is going on.
We have a non-member function, which returns a temporary object of this class. So it calls the constructor
and then passes that to return.
So this will create a temporary object of class Test. In the normal course of events,
this would be copied into the function's return space.
In the main function, we use this return value to initialize a variable of this class, so that is a
copy initialization.
So that will call the copy constructor, again with the temporary object in the return space as the argument.
So that is 2 calls to the copy constructor.
So we expect to see "Copying" appear twice.
Or do we? What do you think will happen?
So we get no calls at all. So let's try this again with GCC.
I have the same code here.
If we compile this with this option, -f-no-elide-constructors,
and then we run it.
So we do get the two calls to the copy constructor. So we have the default constructor call in here
and then that will copy the temporary object into the return space, and then it will copy it again
into the Test object in main(). If I compile it without that option, then we get the same result
as we did with Visual C++.
So the copy elision has taken place twice. The first time when the temporary object is copied into the function's
return space. That has been elided. And that is known as a return value optimization. And also the copy
initialization of the variable in main() was avoided.
And then the return value optimization, that is when the.. I think I am rather repeating myself here!
But the advantage of this is we have avoided one call to the copy constructor and one call to the
destructor.
And then, similarly, for the value in main(), the copy elision again.
That has avoided another call to the copy constructor and destructor.
So that is 4 special member calls which have been avoided. The disadvantages is that the side effect
of the copy constructor, the one where it prints out the message, has not occurred.
There is also a similar process if you have a local variable with a name and it is returned by value, and
that is known as "Named Return Value Optimization".
This is a bit harder for compilers to do.
Compilers will always do return value optimization for a temporary object.
But when you have a variable with a name,
it can be more difficult for compilers to work out what is happening, especially if there are branches or exceptions
or things like that to confuse the issue.
So here is an example again.
The code is exactly the same, except we are now returning a local variable. So we have this local variable,
which is an object of the class, and we return that by value.
And this time we have a different result.
We see copying once, instead of no times at all.
So there has been a copy constructor call somewhere.
And what has happened is that the compiler has not been able to optimize away the copy of the local
variable in the function, into the function's return space.
So it has actually had to call the copy constructor there. But in the actual initialization of the variable
in main(), it has been able to optimize away that copy.
So we see it once.
And finally, the copy elision can also occur when a temporary object is passed by value.
So here we have the same class again.
We have a function which takes an object of this class by value, so it is going to be copied into the
function argument.
We have a main() function, which calls this function, and it passes a temporary object as the argument.
So normally this temporary object would be copied into the function argument, so we would expect to see one
occurrence of "Copying" in the output.
So what do you think will happen this time?
I think you might have guessed that the answer is zero, again.
If I do this in g++, again with the -f-no-elide-constructors option.
Then we get to one call to "Copying".
So that's one call to the copy constructor that has been elided.
Okay, so that is it for this feature.
I will see you next time, but until then, keep coding!