Reputation: 6959
Given
struct Range{
Range(double from, double to) : from(from), to(to) {}
double from;
double to;
};
struct Box{
Box(Range x, Range y) : x(x), y(y) {}
Range x;
Range y;
};
suppose we run Box box(Range(0.0,1.0),Range(0.0,2.0))
.
Could a modern compiler with optimizations enabled avoid copying Range
objects altogether during this construction? (i.e. construct the Range
objects inside box
to begin with?)
Upvotes: 25
Views: 7532
Reputation: 110108
There are actually two copies being performed on each Range
object passed to the constructor. The first happens when copying the temporary Range
object into the function parameter. This can be elided as per the reference given in 101010's answer. There are specific circumstances in which copy elision can be performed.
The second copy happens when copying the function parameter into the member (as specified in the constructor initialization list). This cannot be elided, and this is why you still see a single copy being made for each parameter in YSC's answer.
When the copy constructor has side-effects (such as the prints in YSC's answer), copy elision can still be performed for the first copy, but the second copy must remain.
However, the compiler is always free to make changes if they do not alter the observed behavior of the program (this is known as the "as-if" rule). This means that if the copy constructor has no side effects and removing the constructor call will not change the result, the compiler is free to remove even the second copy.
You can see this by analyzing the generated assembly. In this example, the compiler optimizes out not only the copies, but even the construction of the Box
object itself:
Box box(Range(a,b),Range(c,d));
std::cout << box.x.from;
Generates identical assembly as:
std::cout << a;
Upvotes: 30
Reputation: 42899
Yes it can, In particular this kind of copy elision context falls under the copy elision criterion specified in 12.8/p31.3 Copying and moving class objects [class.copy] of the standard:
(31.3) -- when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same type (ignoring cv-qualification), the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move.
Any descent compiler apply copy elision in this particular context. However, in the OP example two copies taking place.
Box
constructor's initializer list (That can't be elided).You can see it in this demo where the copy constructor is evoked only 2 times.
Have also in mind that, because the standard allows in a particular context copy elision optimization, doesn't mean that a compiler vendor is obligated to do it. Copy elision is the only allowed form of optimization that can change the observable side-effects. Consequently, due to the fact that some compilers do not perform copy elision in every situation where it is allowed (e.g., in debug mode), programs that rely on the side-effects of copy/move constructors and destructors are not portable.
Upvotes: 6
Reputation: 5856
The fact that it can, doesn't mean it most certainly will. See it in this Demo, it's obvious you are creating two copies. Hint, the output contains twice :
copy made
copy made
Upvotes: 1
Reputation: 40080
It should, but I fail to make it work (live example). The compiler may detect the side-effect of the constructors and decide not to go with copy elision.
#include <iostream>
struct Range{
Range(double from, double to) : from(from), to(to) { std::cout << "Range(double,double)" << std::endl; }
Range(const Range& other) : from(other.from), to(other.to) { std::cout << "Range(const Range&)" << std::endl; }
double from;
double to;
};
struct Box{
Box(Range x, Range y) : x(x), y(y) { std::cout << "Box(Range,Range)" << std::endl; }
Box(const Box& other) : x(other.x), y(other.y) { std::cout << "Box(const Box&)" << std::endl; }
Range x;
Range y;
};
int main(int argc, char** argv)
{
(void) argv;
const Box box(Range(argc, 1.0), Range(0.0, 2.0));
std::cout << box.x.from << std::endl;
return 0;
}
Compile & run:
clang++ -std=c++14 -O3 -Wall -Wextra -pedantic -Werror -pthread main.cpp && ./a.out
Output:
Range(double,double)
Range(double,double)
Range(const Range&)
Range(const Range&)
Box(Range,Range)
1
Upvotes: 5