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.
Related
Consider the following class:
struct IntPointerWrapper
{
int* data_pointer;
IntPointerWrapper() : data_pointer(new int())
{
std::cout << "IntPointerWrapper()" << std::endl;
}
IntPointerWrapper(const IntPointerWrapper& other) : data_pointer(new int())
{
std::cout << "IntPointerWrapper(const IntPointerWrapper& other)" << std::endl;
*data_pointer = *other.data_pointer;
}
IntPointerWrapper& operator=(const IntPointerWrapper& other)
{
std::cout << "operator=(const IntPointerWrapper& other)" << std::endl;
*data_pointer = *other.data_pointer;
return *this;
}
~IntPointerWrapper()
{
std::cout << "~IntPointerWrapper() " << std::endl;
delete data_pointer;
}
};
And a simple factory method
IntPointerWrapper bar()
{
IntPointerWrapper q;
return q;
}
int main()
{
IntPointerWrapper m = bar();
}
I want to compile with disabled copy elision, to see how many times the copy constructor is called.
I get this output:
IntPointerWrapper()
IntPointerWrapper(const IntPointerWrapper& other)
~IntPointerWrapper()
IntPointerWrapper(const IntPointerWrapper& other)
~IntPointerWrapper()
~IntPointerWrapper()
So the copy constructor is called twice, but I'm not really getting why. Before running this experiment I would have bet that a single call were enough.
I'm wondering if there is any motivation behind these 2 copies instead of a single one, or it just something implementation specific.
I tried with Clang, GCC and MSVC and I obtain the same result.
With
IntPointerWrapper bar()
{
IntPointerWrapper q;
return q;
}
you have a copy operation to copy q into the return object of the function. That is your first copy. Then you have
IntPointerWrapper m = bar();
which copies the returned value from bar() into m, so that is your second copy operation.
It should be noted that the two copies only happens before C++17. Since C++17 we have guaranteed copy elision and that gets rid of the copy that happens in IntPointerWrapper m = bar(); and only has a copy for the returned object. You can see that difference in this live example
Consider the following C++-Code
#include <iostream>
using namespace std;
struct WrapMe
{
WrapMe() { cout << "WrapMe Default Ctor of: " << this << endl; }
WrapMe(const WrapMe& other) { cout << "WrapMe Copy Ctor of " << this << " from " << &other << endl; }
WrapMe(WrapMe&& other) noexcept { cout << "WrapMe Move Ctor of " << this << " from " << &other << endl; }
~WrapMe() { cout << "Wrap Me Dtor of" << this << endl; }
};
struct Wrapper1
{
WrapMe& data()& { return member; }
WrapMe data()&& { return std::move(member); }
WrapMe member;
};
struct Wrapper2
{
WrapMe& data()& { return member; }
WrapMe&& data()&& { return std::move(member); }
WrapMe member;
};
int main()
{
auto wrapMe1 = Wrapper1().data();
auto wrapMe2 = Wrapper2().data();
}
with the output
WrapMe Default Ctor of: 00000092E7F2F8C4
WrapMe Move Ctor of 00000092E7F2F7C4 from 00000092E7F2F8C4
Wrap Me Dtor of00000092E7F2F8C4
WrapMe Default Ctor of: 00000092E7F2F8E4
WrapMe Move Ctor of 00000092E7F2F7E4 from 00000092E7F2F8E4
Wrap Me Dtor of00000092E7F2F8E4
[...]
Which is the correct way to move from the WrapMe member: Like Wrapper1 (return by value) or like Wrapper2 (return by rvalue-reference) does? Or are both ways equivalent here, as the ouput suggests? If not, why?
WrapMe&& data()&& { return std::move(member); }
This doesn't actually move anything. It just returns a rvalue reference to the member. For example I could do
auto&& wrapMe2 = Wrapper2().data();
and now wrapMe2 will be a dangling reference or
auto w = wrapper2();
auto&& wrapMe2 = std::move(x).data();
Now I have a reference to the member of w without w having changed at all.
Only because the move constructor of WrapMe is called to initialize the wrapMe2 object in the original line, a move operations actually takes place.
The version
WrapMe data()&& { return std::move(member); }
calls the move constructor already to construct the return value. The returned value will never refer to the original object or its member.
The line
auto wrapMe1 = Wrapper1().data();
then calls the move constructor again to initialize wrapMe1 from the return value of .data(). The compiler is however allowed to elide this second move constructor call and instead construct wrapMe1 directly from the expression in the return statement. This is why you see the same result.
With C++17 or later this elision would even be mandatory.
I don't know your use case, so I cannot be sure what the correct approach is, but just guessing on what you want to do:
For consistency between the two overloads, I would use the reference-returning version. Having data provide modifiable access to the member for lvalues, but not for rvalues, would be confusing.
However, you don't really need a member function to do this. Through data the caller has full control over the member anyway, so you could just make the member public from the start and then it could be used in the same way directly.
This question already has answers here:
Why is value taking setter member functions not recommended in Herb Sutter's CppCon 2014 talk (Back to Basics: Modern C++ Style)?
(4 answers)
Closed 7 years ago.
Assume I have the following class, which has a method set_value. Which implementation is better?
class S {
public:
// a set_value method
private:
Some_type value;
};
Pass by value, then move
void S::set_value(Some_type value)
{
this->value = std::move(value);
}
Define two overloaded methods
void S::set_value(const Some_type& value)
{
this->value = value;
}
void S::set_value(Some_type&& value)
{
this->value = std::move(value);
}
The first approach requires definition of one method only while the second requires two.
However, the first approach seems to be less efficient:
Copy/Move constructor for the parameter depending on the argument passed
Move assignment
Destructor for the parameter
While for the second approach, only one assignment operation is performed.
Copy/Move assignment depending on which overloaded method is called
So, which implementation is better? Or does it matter at all?
And one more question: Is the following code equivalent to the two overloaded methods in the second approach?
template <class T>
void S::set_value(T&& value)
{
this->value = std::forward<T>(value);
}
The compiler is free to elide (optimise away) the copy even if there would be side effects in doing so. As a result, passing by value and moving the result actually gives you all of the performance benefits of the two-method solution while giving you only one code path to maintain. You should absolutely prefer to pass by value.
here's an example to prove it:
#include <iostream>
struct XYZ {
XYZ() { std::cout << "constructed" << std::endl; }
XYZ(const XYZ&) {
std::cout << "copy constructed" << std::endl;
}
XYZ(XYZ&&) noexcept {
try {
std::cout << "move constructed" << std::endl;
}
catch(...) {
}
}
XYZ& operator=(const XYZ&) {
std::cout << "assigned" << std::endl;
return *this;
}
XYZ& operator=(XYZ&&) {
std::cout << "move-assigned" << std::endl;
return *this;
}
};
struct holder {
holder(XYZ xyz) : _xyz(std::move(xyz)) {}
void set_value(XYZ xyz) { _xyz = std::move(xyz); }
void set_value_by_const_ref(const XYZ& xyz) { _xyz = xyz; }
XYZ _xyz;
};
using namespace std;
auto main() -> int
{
cout << "** create named source for later use **" << endl;
XYZ xyz2{};
cout << "\n**initial construction**" << std::endl;
holder h { XYZ() };
cout << "\n**set_value()**" << endl;
h.set_value(XYZ());
cout << "\n**set_value_by_const_ref() with nameless temporary**" << endl;
h.set_value_by_const_ref(XYZ());
cout << "\n**set_value() with named source**" << endl;
h.set_value(xyz2);
cout << "\n**set_value_by_const_ref() with named source**" << endl;
h.set_value_by_const_ref(xyz2);
return 0;
}
expected output:
** create named source for later use **
constructed
**initial construction**
constructed
move constructed
**set_value()**
constructed
move-assigned
**set_value_by_const_ref() with nameless temporary**
constructed
assigned
**set_value() with named source**
copy constructed
move-assigned
**set_value_by_const_ref() with named source**
assigned
note the absence of any redundant copies in the copy/move versions but the redundant copy-assignment when calling set_value_by_const_ref() with nameless temporary. I note the apparent efficiency gain of the final case. I would argue that (a) it's a corner case in reality and (b) the optimiser can take care of it.
my command line:
c++ -o move -std=c++1y -stdlib=libc++ -O3 move.cpp
It seems the way to construct objects in C++0x avoiding copies/moves (particularly for large stack allocated objects) is "pass by lambda".
See the following code:
#include <iostream>
#define LAMBDA(x) [&] { return x; }
class A
{
public:
A() {};
A(const A&) { std::cout << "Copy "; }
A(A&&) { std::cout << "Move "; }
};
class B1
{
public:
B1(const A& a_) : a(a_) {}
B1(A&& a_) : a(std::move(a_)) {}
A a;
};
class B2
{
public:
B2(const A& a_) : a(a_) {}
B2(A&& a_) : a(std::move(a_)) {}
template <class LAMBDA_T>
B2(LAMBDA_T&& f, decltype(f())* dummy = 0) : a(f()) {}
A a;
};
int main()
{
A a;
std::cout << "B1 b11( a ): ";
B1 b11(a);
std::cout << std::endl;
std::cout << "B2 b12(LAMBDA(a)): ";
B2 b12(LAMBDA(a));
std::cout << std::endl;
std::cout << std::endl;
std::cout << "B1 b21( std::move(a) ): ";
B1 b21(std::move(a));
std::cout << std::endl;
std::cout << "B2 b22(LAMBDA(std::move(a))): ";
B2 b22(LAMBDA(std::move(a)));
std::cout << std::endl;
std::cout << std::endl;
std::cout << "B1 b31(( A() )): ";
B1 b31((A()));
std::cout << std::endl;
std::cout << "B2 b32((LAMBDA(A()))): ";
B2 b32((LAMBDA(A())));
std::cout << std::endl;
std::cout << std::endl;
}
Which outputs the following:
B1 b11( a ): Copy
B2 b12(LAMBDA(a)): Copy
B1 b21( std::move(a) ): Move
B2 b22(LAMBDA(std::move(a))): Move
B1 b31(( A() )): Move
B2 b32((LAMBDA(A()))):
Note the "pass by lambda" removes the move in the case where the parameter is a what I believe is called a "prvalue".
Note that it seems the "pass by lambda" approach only helps when the parameter is a "prvalue", but it doesn't seem to hurt in other cases.
Is there anyway to get functions to accept "pass by lambda" parameters in C++0x, that is nicer than the client having to wrap their parameters in lambda functions themselves? (other than defining a proxy macro that calls the function).
If you're okay with a templated constructor, you might as well use perfect forwarding instead of the obfuscation with lambdas.
class super_expensive_type {
public:
struct token_t {} static constexpr token = token_t {};
super_expensive_type(token_t);
}
constexpr super_expensive_type::token_t super_expensive_type::token;
class user {
public:
template<typename... Args>
explicit
user(Args&&... args)
: member { std::forward<Args>(args)... }
{}
private:
super_expensive_type member;
};
// ...
// only one construction here
user { super_expensive_type::token };
super_expensive_type moved_from = ...;
// one move
user { std::move(moved_from) };
super_expensive_type copied_from = ...;
// one copy
user { copied_from };
Using lambdas can't be better than this because the result from the expression in the lambda body has to be returned.
There's a fundamental problem with what you're doing. You cannot magic an object into existence. The variable must be:
Default constructed
Copy constructed
Move constructed
Constructed with a different constructor.
4 is off the table, since you only defined the first three. Your copy and move constructors both print things. Therefore, the only conclusion one can draw is that, if nothing is printed, the object is being default constructed. IE: filled with nothing.
In short, your Lambda-based transfer mechanism doesn't seem to be transferring anything at all.
After further analysis, I see what's happening. Your lambda isn't actually taking a value by reference; it's constructing a value. If you expand the macro, what you get is this:
B2 b32(([&] {return A()}));
It constructs a temporary; it doesn't actually take anything by reference. So I'm not sure how you can consider this "passing" anything. All you're doing is making a function that constructs an object. You could just as easily pass the arguments for B2::a's constructor to the constructor of B2 and have it use them to create the object, and it would give you the same effect.
You're not passing a value. You're making a function that will always create the exact same object. That's not very useful.
I run this code for experimenting copy constructor and assignment operator
class AClass {
private:
int a;
public:
AClass (int a_) : a(a_) {
cout << " constructor AClass(int) " << a << endl;
}
AClass(const AClass & x) : a(x.a) {
cout << " copy constructor AClass(const AClass &) " << a << endl;
}
AClass & operator=(const AClass & x) {
a = x.a;
cout << " AClass& operator=(const AClass &) " << a - endl;
return *this;
}
};
AClass g () {
AClass x(8);
return x;
}
int main () {
cout << " before AClass b = g() " << endl;
AClass b = g();
cout << " after" << endl;
cout << " before AClass c(g()) " << endl;
AClass c (g());
cout << " after" << endl;
}
and found that no message appears for the return x;
Why?
Should not the copy constructor or operator= be called?
This is the output:
before AClass b = g()
constructor AClass(int) 8
after
before AClass c(g())
constructor AClass(int) 8
after
The compiler is allowed to elide copying in a case like this. This is called Return Value Optimization.
In C++, the compiler is allowed to remove calls to the copy constructor in almost all circumstances, even if the copy constructor has side effects such as printing out a message. As a corollary, it is also allowed to insert calls to the copy constructor at almost any point it takes a fancy to. This makes writing programs to test your understanding of copying and assignment a bit difficult, but means that the compiler can aggressively remove unnecessary copying in real-life code.
This is known as "return value optimisation". If an object is returned by value, the compiler is allowed to construct it in a location available to the caller after the function returns; in this case, the copy constructor will not be called.
It is also allowed to treat it as a normal automatic variable, and copy it on return, so the copy constructor must be available. Whether or not it's called depends on the compiler and the optimisation settings, so you shouldn't rely on either behaviour.
This is called Copy Ellision. The compiler is allowed to ellide copies in virtually any situation. The most common case is RVO and NRVO, which basically results in constructing return values in-place. I'll demonstrate the transformation.
void g (char* memory) {
new (memory) AClass(8);
}
int main () {
char __hidden__variable[sizeof(AClass)];
g(__hidden__variable);
AClass& b = *(AClass*)&__hidden__variable[0];
cout -- " after" -- endl;
// The same process occurs for c.
}
The code has the same effect, but now only one instance of AClass exists.
The compiler may have optimized away the copy constructor call. Basically, it moves the object.
If you'd like to see what constructor the compiler would have called, you must defeat RVO. Replace your g() function thus:
int i;
AClass g () {
if(i) {
AClass x(8);
return x;
} else {
AClass x(9);
return x;
}
}