Why can copy elision not optimise all copy operations? - c++

I read about copy elision and how it can fasten up Programms by giving possibilities to write code more straight foreword without thinking about references of variables.
In a small example I tried to find the limits of this technique.
#include <iostream>
class A
{
public:
A(){}
A(const A &a) {std::cout << "Copy" << std::endl;}
};
A foo(A a)
{
return a;
}
int main(void)
{
A a = foo(foo(A()));
std::cout << std::endl;
A b;
b = foo(foo(b));
return 0;
}
https://godbolt.org/z/xo88jq
Output:
Copy
Copy
Copy
Copy
Copy
The compiler already elides many of the copies in this example.
But why is the compiler not able to elide all those copies leading to a or in the modification of b?

Related

What is the best way to return multiple large objects in C++?

I want to return a tuple containing types like std::vector or std::unordered_map etc. where the objects may be large enough that I care about not copying. I wasn't sure how copy elision / return value optimization will work when the returned objects are wrapped in a tuple. To this end I wrote some test code below and am confused by parts of its output:
#include <tuple>
#include <iostream>
struct A {
A() {}
A(const A& a) {
std::cout << "copy constructor\n";
}
A(A&& a) noexcept {
std::cout << "move constructor\n";
}
~A() {
std::cout << "destructor\n";
}
};
struct B {
};
std::tuple<A, B> foo() {
A a;
B b;
return { a, b };
}
std::tuple<A, B> bar() {
A a;
B b;
return { std::move(a), std::move(b) };
}
std::tuple<A, B> quux() {
A a;
B b;
return std::move(std::tuple<A, B>{ std::move(a), std::move(b) });
}
std::tuple<A, B> mumble() {
A a;
B b;
return std::move(std::tuple<A, B>{ a, b });
}
int main()
{
std::cout << "calling foo...\n\n";
auto [a1, b1] = foo();
std::cout << "\n";
std::cout << "calling bar...\n\n";
auto [a2, b2] = bar();
std::cout << "\n";
std::cout << "calling quux...\n\n";
auto [a3, b3] = quux();
std::cout << "\n";
std::cout << "calling mumble...\n\n";
auto [a4, b4] = mumble();
std::cout << "\n";
std::cout << "cleaning up main()\n";
return 0;
}
when I run the above (on VS2019) I get the following output:
calling foo...
copy constructor
destructor
calling bar...
move constructor
destructor
calling quux...
move constructor
move constructor
destructor
destructor
calling mumble...
copy constructor
move constructor
destructor
destructor
cleaning up main()
destructor
destructor
destructor
destructor
So from the above is looks like bar() is best which is return { std::move(a), std::move(b) }. My main question is why foo() ends up copying? RVO should elide the tuple from being copied but shouldn't the compiler be smart enough to not copy the A struct? The tuple constructor could be a move constructor there since it is firing in an expression that is being returned from a function i.e. because struct a is about to not exist.
I also don't really understand what is going on with quux(). I didnt think that additional std::move() call was necessary but I don't understand why it ends up causing an additional move to actually occur i.e. I'd expect it to have the same output as bar().
My main question is why foo() ends up copying? RVO should elide the
tuple from being copied but shouldn't the compiler be smart enough to
not copy the A struct? The tuple constructor could be a move
constructor
No, move constructor could only construct it from another tuple<> object. {a,b} is constructing from the component types, so the A and B objects are copied.
what it going on with quux(). I didnt think that additional
std::move() call was necessary but I don't understand why it ends up
causing an additional move to actually occur i.e. I'd expect it to
have the same output as bar().
The 2nd move happens when you are moving the tuple. Moving it prevents the copy elision that occurs in bar(). It is well-know that std::move() around the entire return expression is harmful.

C++ copy elision of fields

I am trying to get copy elision to work for fields of the object that is to be returned.
Example code:
#include <iostream>
struct A {
bool x;
A(bool x) : x(x) {
std::cout << "A constructed" << std::endl;
}
A(const A &other) : x(other.x) {
std::cout << "A copied" << std::endl;
}
A(A &&other) : x(other.x) {
std::cout << "A moved" << std::endl;
}
A &operator=(const A &other) {
std::cout << "A reassigned" << std::endl;
if (this != &other) {
x = other.x;
}
return *this;
}
};
struct B {
A a;
B(const A &a) : a(a) {
std::cout << "B constructed" << std::endl;
}
B(const B &other) : a(other.a) {
std::cout << "B copied" << std::endl;
}
B(B &&other) : a(other.a) {
std::cout << "B moved" << std::endl;
}
B &operator=(const B &other) {
std::cout << "B reassigned" << std::endl;
if (this != &other) {
a = other.a;
}
return *this;
}
};
B foo() {
return B{A{true}};
}
int main() {
B b = foo();
std::cout << b.a.x << std::endl;
}
I compile with:
g++ -std=c++17 test.cpp -o test.exe
output:
A constructed
A copied
B constructed
1
B is constructed in-place. Why is A not? I would at least expect it to be move-constructed, but it is copied instead.
Is there a way to also construct A in-place, inside the B to be returned? How?
Constructing a B from an A involves copying the A - it says so in your code. That has nothing to do with copy elision in function returns, all of this happens in the (eventual) construction of B. Nothing in the standard allows eliding (as in "breaking the as-if rule for") the copy construction in member initialization lists. See [class.copy.elision] for the handful of circumstances where the as-if rule may be broken.
Put another way: You get the exact same output when creating B b{A{true}};. The function return is exactly as good, but not better.
If you want A to be moved instead of copied, you need a constructor B(A&&) (which then move-constructs the a member).
You will not succeed at eliding that temporary in its current form.
While the language does try to limit the instantiation ("materialisation") of temporaries (in a way that is standard-mandated and doesn't affect the as-if rule), there are still times when your temporary must be materialized, and they include:
[class.temporary]/2.1: - when binding a reference to a prvalue
You're doing that here, in the constructor argument.
In fact, if you look at the example program in that paragraph of the standard, it's pretty much the same as yours and it describes how the temporary needn't be created in main then copied to a new temporary that goes into your function argument… but the temporary is created for that function argument. There's no way around that.
The copy to member then takes place in the usual manner. Now the as-if rule kicks in, and there's simply no exception to that rule that allows B's constructor's semantics (which include presenting "copied" output) to be altered in the way you were hoping.
You can check the assembly output for this, but I'd guess without the output there will be no need to actually perform any copy operations and the compiler can elide your temporary without violating the as-if rule (i.e. in the normal course of its activities when creating a computer program, from your C++, which is just an abstract description of a program). But then that's always been the case, and I guess you know that already.
Of course, if you add a B(A&& a) : a(std::move(a)) {} then you move the object into the member instead, but I guess you know that already too.
I have figured how to do what I wanted.
The intent was to return multiple values from a function with the minimal amount of "work".
I try to avoid passing return values as writable references (to avoid value mutation and assignment operators), so I wanted to do this by wrapping the objects to be returned in a struct.
I have succeeded at this before, so I was surprised that the code above didn't work.
This does work:
#include <iostream>
struct A {
bool x;
explicit A(bool x) : x(x) {
std::cout << "A constructed" << std::endl;
}
A(const A &other) : x(other.x) {
std::cout << "A copied" << std::endl;
}
A(A &&other) : x(other.x) {
std::cout << "A moved" << std::endl;
}
A &operator=(const A &other) {
std::cout << "A reassigned" << std::endl;
if (this != &other) {
x = other.x;
}
return *this;
}
};
struct B {
A a;
};
B foo() {
return B{A{true}};
}
int main() {
B b = foo();
std::cout << b.a.x << std::endl;
}
output:
A constructed
1
The key was to remove all the constructors of B. This enabled aggregate initialization, which seems to construct the field in-place. As a result, copying A is avoided. I am not sure if this is considered copy elision, technically.

constructor and copy constructor behaviour in c++ [duplicate]

This question already has answers here:
Why copy constructor is not called here?
(2 answers)
Closed 8 years ago.
I have the following code:
#include <iostream>
using namespace std;
class X
{
public:
int g;
X() { cout << "constr" << endl; }
X(const X& ref1) { cout << "copy constr" << endl; }
};
X f()
{
X ee;
ee.g = 1;
return ee;
}
int main()
{
X ff = f();
return 0;
}
Running the code I see that the constructor was called only once and the copy constructor was never called. Don't you expect two constructor and one copy constructor calls here? Thanks!
This is a special case of the copy elision called return value optimization (the link explains precisely your case).
Copy Elision is an optimization implemented by many compilers to prevent extra, unnecessary, copies. Makes the return-by-value or pass-by-value possible in practice.
Take a look at the example in the following answer: https://stackoverflow.com/a/12953129/1938163
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
(http://en.wikipedia.org/wiki/Return_value_optimization#Summary)
Perhaps incredible to believe the first time, depending on the compiler & settings, the following outputs are all valid:
Hello World!
A copy was made.
A copy was made.
Hello World!
A copy was made.
Hello World!
In your case, this is a special copy elision optimization called RVO - Return Value Optimization where an object returned by value from a method has its copy elided.

Why is copy-constructor not called in this case?

I encountered a code snippet and thought that it would call copy-constructor but in contrast , it simply called normal constructor . Below is the code
#include <iostream>
using namespace std;
class B
{
public:
B(const char* str = "\0")
{
cout << "Constructor called" << endl;
}
B(const B &b)
{
cout << "Copy constructor called" << endl;
}
};
int main()
{
B ob = "copy me";
return 0;
}
What you've discovered that B ob = "copy me"; notionally creates a B from the literal and then copy constructs ob, but that the compiler is allowed to elide the copy and construct directory into ob. g++ even elides the copy with no optimization enabled at all.
You can observe that this is the case by making your copy constructor private: The code will fail to compile even though the compiler won't actually use the copy constructor (the standard requires that copy constructors be accessible even when the call is elided).

No call to the copy constructor

Consider this:
#include <iostream>
struct A{
A(){
std::cout << "Create empty A" << std::endl;
}
A(const A& a){
// Why is this never called??
std::cout << "Never called" << std::endl;
}
};
A genA() {
A a;
return a;
}
int main(int argc, const char *argv[])
{
A a(genA()); // Expected to call copy constructor
return 0;
}
Why is the copy constructor not called?
What should I do if I want to ensure that "Never called" is printed on the screen every time I copy A.
This is called as Return value optimization.
Compiler can optimize your code so that it bulds the object directly in the location where it would have been copied too. Thus there will be no reason to use the copy constructor.
Note: The standard explicitly allows it do so.
One way you can force the compiler to avoid RVO is to not return by value - e.g.
#include <iostream>
#include <memory>
struct A{
A() {
std::cout << "Create empty A" << std::endl;
}
A(const A& a) {
// This will be called now
std::cout << "Never called" << std::endl;
}
};
std::auto_ptr<A> genA() {
return std::auto_ptr<A>(new A);
}
int main(int argc, const char *argv[])
{
A a(*(genA().get())); // this will trigger the copy-ctor
return 0;
}
Though this is a horrible hack. I would instead ask why you want to do this? and if you are hoping to put some side-effects into the constructor, urge you to think otherwise.
Copy constructor invocations can be elided (even if they contain side effects), the standard allows that ([12.2]).
EDIT:
I suggest that you do not try to fight it in real world code.
If you just want to see the copy ctor executed in some example/turorial code, then it usually helps not to compile with optimization. Some compilers even have switches that help to avoid that. For GCC it should be -fno-elide-constructors.