C++ copy on member access - c++

I was doing some experiments to see when copy is performed apart from copy elision, RVO, NRVO cases.
So I've written some code like this:
class X {
public:
X() { std::cout << "Default constructor" << std::endl; }
X(const X&) { std::cout << "Copy constructor" << std::endl; }
X(X&&) { std::cout << "Move constructor" << std::endl; }
X& operator=(const X) {
std::cout << "Assignment operator" << std::endl;
return *this;
}
X& operator=(X&&) {
std::cout << "Move assignment operator" << std::endl;
return *this;
}
~X() { std::cout << "Destructor" << std::endl; }
};
class Y {
private:
X x;
public:
const X& getX() const {
std::cout << "getX" << std::endl;
return x;
}
};
int main() {
Y y;
std::cout << "assign to ref" << std::endl;
const X& x1 = y.getX();
(void)x1;
std::cout << "assign to const" << std::endl;
const X x2 = y.getX();
return 0;
}
and I receive the following as output:
Default constructor
assign to ref
getX
assign to const
getX
Copy constructor
Destructor
Destructor
Both when compiled with gcc or clang with -O3 and tried -std=c++{11,14,17} all produced the same output.
Which surprised me was, I wasn't expecting any copy to be performed when using y.getX(); to a const variable. It is something I used frequently just to ease my access to that variable and its members in the following code, but I wasn't doing it over a const reference instead I was just using const hoping the compiler would regard it just as a renaming.
Does anyone knows why exactly is that copy performed? Only reason that comes to my mind is that it is to make code thread-safe. If there are multiple threads working with object y, then my assignment to const would not be that const after all. Since it would just reference the member x in object y. Which might be changed by other threads. But I am not sure whether that's the real intention or not.

To see the effect of RVO verses compiler forced use of NRVO, play with -fno-elide-constructors compiler switch on the following modified program below. With the usual options you get:
Default constructor 1
assign to ref
getX (with id: 1)
x1 (id:1)
assign to const
getX (with id: 1)
Copy constructor 2
x2 (id:2)
make_X copy
Default constructor 3
make_X (with id: 3)
x3 (id:3)
make_X ref
Default constructor 4
make_X (with id: 4)
x4 (id:4)
Destructor 4
Destructor 3
Destructor 2
Destructor 1
But with NRVO you get:
Default constructor 1
assign to ref
getX (with id: 1)
x1 (id:1)
assign to const
getX (with id: 1)
Copy constructor 2
x2 (id:2)
additional 1
Default constructor 3
make_X (with id: 3)
Move constructor 4
Destructor 3
Move constructor 5
Destructor 4
x3 (id:5)
additional 2
Default constructor 6
make_X (with id: 6)
Move constructor 7
Destructor 6
x4 (id:7)
Destructor 7
Destructor 5
Destructor 2
Destructor 1
Code example:
#include <iostream>
int global_id;
class X {
public:
X() : id(++global_id) {
std::cout << "Default constructor " << id << std::endl;
}
X(const X&) : id(++global_id) {
std::cout << "Copy constructor " << id << std::endl;
}
X(X&&) : id(++global_id) {
std::cout << "Move constructor " << id << std::endl;
}
X& operator=(const X&) {
std::cout << "Assignment operator " << id << std::endl;
return *this;
}
X& operator=(X&&) {
std::cout << "Move assignment operator " << id << std::endl;
return *this;
}
~X() {
std::cout << "Destructor " << id << std::endl;
}
int id;
};
class Y {
X x;
public:
const X& getX() const {
std::cout << "getX (with id: " << x.id << ')' << std::endl;
return x;
}
X make_X() const {
X extra;
std::cout << "make_X (with id: " << extra.id << ')' << std::endl;
return extra;
}
};
int main()
{
Y y;
std::cout << "assign to ref" << std::endl;
const X& x1 = y.getX();
std::cout << "x1 (id:" << x1.id << ")\n";
(void) x1;
std::cout << "assign to const" << std::endl;
const X x2 = y.getX();
std::cout << "x2 (id:" << x2.id << ")\n";
std::cout << "make_X copy" << std::endl;
const X x3 = y.make_X();
std::cout << "x3 (id:" << x3.id << ")\n";
std::cout << "make_X ref" << std::endl;
const X& x4 = y.make_X();
std::cout << "x4 (id:" << x4.id << ")\n";
return 0;
}
As you see, the RVO really only comes to play with local variables.

Related

Constructing elements of union

The following class outputs when a constructor is called:
class A {
public:
A() {
std::cout << "Default Constructor called at address:" << this << "!\n";
}
A(int val) : val_(val) {
std::cout << "Secondary Constructor called at address:" << this << "\n";
}
A(A&& other) : val_(std::move(other.val_)) {
std::cout << "Move constructor called from " << &other << " to " << this << "\n";
}
A& operator=(A&& other) {
std::cout << "Move assignment called from " << &other << " to " << this << "\n";
val_ = std::move(other.val_);
return *this;
}
private:
int val_;
};
This union allows me to skip the default constructor of A, which is desirable:
union MyUnion {
MyUnion() {}
A a;
};
My goal with main was to move-construct a. Instead it move-assigns the a.
int main(){
MyUnion my_union[2];
std::cout << "Entering for loop:\n";
for (int i = 0; i < 2; ++i){
my_union[i].a = std::move(A(i));
}
return 0;
}
An example output from the above main function is:
Entering for loop:
Secondary Constructor called at address:0x7ffcdd3553a8
Move assignment called from 0x7ffcdd3553a8 to 0x7ffcdd3553b0
Secondary Constructor called at address:0x7ffcdd3553a8
Move assignment called from 0x7ffcdd3553a8 to 0x7ffcdd3553b4
I wanted to move-construct, but I'm instead move-assigning. Move-assigning without move constructing is unsafe in general. The solution I came up with was to use std::construct_at. I also added a destructor. The full program with these modifications is below:
#include <iostream>
#include <memory>
#include <utility>
class A {
public:
A() {
std::cout << "Default Constructor called at address:" << this << "!\n";
}
A(int val) : val_(val) {
std::cout << "Secondary Constructor called at address:" << this << "\n";
}
A(A&& other) : val_(std::move(other.val_)) {
std::cout << "Move constructor called from " << &other << " to " << this << "\n";
}
A& operator=(A&& other) {
std::cout << "Move assignment called from " << &other << " to " << this << "\n";
val_ = std::move(other.val_);
return *this;
}
~A() {
std::cout << "Destructor called at " << this << "\n";
}
private:
int val_;
};
union MyUnion {
MyUnion() {
std::cout << "MyUnion Constructor called!\n";
}
void ConstructAt(A&& other){
std::construct_at(&a, std::forward<A>(other));
}
A a;
~MyUnion(){
std::cout << "MyUnion Destructor called!\n";
a.~A();
}
};
int main(){
MyUnion my_union[2];
std::cout << "\nEntering for loop:\n";
for (int i = 0; i < 2; ++i){
my_union[i].ConstructAt(std::move(A(i)));
}
std::cout << "\nExiting for loop:\n";
return 0;
}
This gives output:
MyUnion Constructor called!
MyUnion Constructor called!
Entering for loop:
Secondary Constructor called at address:0x7ffe38dd4838
Move constructor called from 0x7ffe38dd4838 to 0x7ffe38dd4840
Destructor called at 0x7ffe38dd4838
Secondary Constructor called at address:0x7ffe38dd4838
Move constructor called from 0x7ffe38dd4838 to 0x7ffe38dd4844
Destructor called at 0x7ffe38dd4838
Exiting for loop:
MyUnion Destructor called!
Destructor called at 0x7ffe38dd4844
MyUnion Destructor called!
Destructor called at 0x7ffe38dd4840
This appears to be what I want.
Question
Was this the correct way to call the move constructor? Am I using std::forward correctly? Is this the correct way to handle the destructor of a union?

Can't erase from vector after having moved out some elements [duplicate]

This question already has answers here:
Why user-defined move-constructor disables the implicit copy-constructor?
(2 answers)
Closed 3 years ago.
Hello I am trying to understand how optimize moving elements from a vector to another checking if that is calling copy or move constructor on the way.
However when I try to erase the elements from a std::vector v1 that I moved out onto another vector v2, the compilers complains with the following message:
vector-move-elements.cpp:19:5: note: copy assignment operator is implicitly deleted because 'Foo' has a
user-declared move constructor
Foo(Foo&& other) {
^
Here is the code
#include <vector>
#include <string>
#include <iostream>
// #include <algorithm> std::copy_if
// inspired from https://stackoverflow.com/questions/15004517/moving-elements-from-stdvector-to-another-one
struct Foo
{
Foo(std::string&& s, uint32_t idx) {
this->name = std::move(s);
this->index = std::move(idx);
std::cout << "Calling default constructor " << name << idx << std::endl;
}
Foo(const Foo& other) {
this->name = other.name;
this->index = other.index;
std::cout << "Calling copy constructor " << other.name << other.index << std::endl;
}
Foo(Foo&& other) {
this->name = std::move(other.name);
this->index = std::move(other.index);
std::cout << "Calling move constructor " << other.name << other.index << std::endl;
}
std::string name;
uint32_t index;
};
int main() {
std::vector<Foo> v1, v2;
v1.reserve(25); // https://stackoverflow.com/questions/52109542/push-back-to-stdvector-the-copy-constructor-is-repeatedly-called
v2.reserve(25); // without this the push_back instruction causes a call to copy constructor
// fill v1 with dummy elements
for (uint32_t i = 0; i < 25; ++i ) {
std::string s = std::string("name_") + std::to_string(i);
std::cout << s << std::endl;
v1.emplace_back(Foo(std::move(s), i));
}
std::cout << "End of construction" << std::endl;
// populate v1 with at least 17 elements...
const auto it = std::next(v1.begin(), 17); // same as v1.begin()+17;
std::move(v1.begin(), it, std::back_inserter(v2));
// std::copy_if(std::make_move_iterator(begin(v1)),
// std::make_move_iterator(end(v1)),
// std::back_inserter(v2),
// [](const Foo& v){ return v.index <= 17; });
// this line DOESN'T COMPILE
v1.erase(v1.begin(), it); // copy assignment operator is implicitly deleted because 'Foo' has a
user-declared move constructor
// if I don't loop on auto& the for loop will call copy constructor
for (auto const& e : v1) {
std::cout << "v1: " << e.name << " " << e.index << std::endl;
}
for (auto const& e : v2) {
std::cout << "v2: " << e.name << " " << e.index << std::endl;
}
}
What you need to check is the rule of three/five/zero.
You need a copy assignment operator to get your code compiled:
Foo& operator=(const Foo& other) {
this->name = other.name;
this->index = other.index;
std::cout << "Calling copy assignment " << other.name << other.index << std::endl;
return *this;
}
and a move assignment operator to adhere to the rule of 5.
Live on godbolt

rvalue as initialiser to construct an object

I am new to programming. sorry for my bad english.
I have tried to use rvalue as initialiser to initial objects.
So, according to the code, it would print out what are the used constructor and assignment operator.
But turned out object "what2" and "what3", those don't print out anything.
here is the code:
#include <iostream>
using namespace std;
class X{
public:
int x;
X()= default;
X(int num):x{num}{}
X(const X& y){
x = y.x;
std::cout << "copy constructor" << std::endl;
std::cout << x << std::endl;
}
X& operator=(const X& d){
x = d.x;
std::cout << "copy assignment" << std::endl;
return *this;
}
X(X&& y){
x = y.x;
std::cout << "move constructor" << std::endl;
}
X& operator=(X&& b){
x = b.x;
std::cout << "move assignment" << std::endl;
return *this;
}
};
X operator +(const X& a,const X& b){
X tmp{};
tmp.x = a.x +b.x;
return tmp;
}
int main(int argc, const char * argv[]) {
X a{7} , b{11};
X what1;
cout << "---------------" << endl;
what1 = a+b;
cout << "---------------" << endl;
X what2{a+b};
cout << "---------------" << endl;
X what3 = a+b;
cout << "---------------" << endl;
std::cout << what1.x << std::endl;
std::cout << what2.x << std:: endl;
std::cout <<what3.x << std::endl;
return 0;
}
the output is:
---------------
move assignment
---------------
---------------
---------------
18
18
18
Program ended with exit code: 0
only "what1" uses assignment properly.
so, how can i use rvalue to initial an object? and using operator= to initial an object?
thank you very much.
Your code could result in move operations being used, but your compiler has chosen to elide those moves and allocate the return of operator+ directly at the call site. You can see this happening if you disable copy elision in your compiler (-fno-elide-constructors in GCC or Clang).
Your move constructor and assignment operator will be successfully used in contexts in which copy elision is not permitted, such as this:
X what2 { std::move(what1) }; //can't elide copy, move constructor used
The following code triggers more of your constructors/operators, check it out to see which trigger in what cases
#include <iostream>
using namespace std;
class X{
public:
int x;
X()
{
x = 0;
std::cout << "constructor" << std::endl;
}
X(int num):x{num}
{
std::cout << "list initilizer" << std::endl;
}
X(const X& y){
x = y.x;
std::cout << "copy constructor" << std::endl;
}
X& operator=(const X& d){
x = d.x;
std::cout << "copy assignment" << std::endl;
return *this;
}
X(X&& y){
x = y.x;
std::cout << "move constructor" << std::endl;
}
X& operator=(X&& b){
x = b.x;
std::cout << "move assignment" << std::endl;
return *this;
}
};
X operator +(const X& a,const X& b){
X tmp{};
tmp.x = a.x +b.x;
return tmp;
}
int main(int argc, const char * argv[]) {
X a{7} , b{11};
cout << "---------------" << endl;
X what1;
cout << "---------------" << endl;
what1 = a+b;
cout << "---------------" << endl;
X what2(a+b);
cout << "---------------" << endl;
X what3 = X(a);
cout << "---------------" << endl;
X what4 = X();
cout << "---------------" << endl;
X what5 = std::move(what1);
cout << "---------------" << endl;
what5 = std::move(what1);
cout << "---------------" << endl;
what5 = what1;
cout << "---------------" << endl;
return 0;
}
GCC provides the -fno-elide-constructors option to disable copy-elision. if you want to elide the copy-elision then use flag -fno-elide-constructors. Refer https://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization/27916892#27916892 for more detail

Is there a way to prevent a move constructor followed by a move assignment operator when copy elison fails?

I've a situation where I want to call a function with a parameter and return the result into this same argument
foo = f(foo);
In addition, I assume that the parameter x is very large, so I don't want to call its copy constructor, but rather its move constructor. Finally, I don't want to pass the argument by reference because I would like to compose the function f with another function g. Hence, so that things like
foo = g(f(foo));
are possible. Now, with move semantics, this is all mostly possible as demonstrated by the following program
#include <iostream>
struct Foo {
Foo() {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
Foo f(Foo && foo) {
std::cout << "Called f" << std::endl;
return std::move(foo);
}
Foo g(Foo && foo) {
std::cout << "Called g" << std::endl;
return std::move(foo);
}
int main() {
Foo foo;
foo = f(std::move(foo));
std::cout << "Finished with f(foo)" << std::endl;
foo = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo))" << std::endl;
}
The output from this program is:
constructor
Called f
move
move assignment
destructor
Finished with f(foo)
Called f
move
Called g
move
move assignment
destructor
destructor
Finished with g(f(foo))
destructor
which makes sense. Now, what's bothering me is that when we call f the first time, or the composition, the move constructor is followed by the move assignment operator. Ideally, I'd like to use copy elison to prevent any of these constructors from being called, but I'm not sure how. Specifically, the functions f and g call std::move on foo because otherwise the copy, not move, constructor is called. This is specified in the C++ standard under section 12.8.31 and 12.8.32. Specifically,
When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the constructor
selected for the copy/move operation and/or the 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 cvunqualified
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
Since we return a function argument, we don't get copy elison. In addition:
When the criteria for elision of a copy operation are met or would be
met save for the fact that the source object is a function parameter,
and the object to be copied is designated by an lvalue, overload
resolution to select the constructor for the copy is first performed
as if the object were designated by an rvalue. If overload resolution
fails, or if the type of the first parameter of the selected
constructor is not an rvalue reference to the object’s type (possibly
cv-qualified), overload resolution is performed again, considering the
object as an lvalue. [ Note: This two-stage overload resolution must
be performed regardless of whether copy elision will occur. It
determines the constructor to be called if elision is not performed,
and the selected constructor must be accessible even if the call is
elided. —end note ]
Since we return a function argument, we return an l-value, so we're forced to use std::move. Now, at the end of the day, I just want the memory moved back into the argument and calling both a move constructor and move assignment operator seems like too much. It feels like there should be a single move or copy elison. Is there a way to accomplish this?
Edit 1
In a longer response to #didierc's answer than a comment would allow, technically, yes, that would work for this situation. At the same time, the greater goal is to allow functions with multiple returns to be composed together in a way where nothing is copied. I can also do this with move semantics, but it requires a trick from C++14 to work. It also exacerbates the issue with lots of moves. However, technically, there's no copies. Specifically:
#include <tuple>
#include <iostream>
#include <utility>
// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}
// Now, for our example
struct Foo {
Foo() {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
std::tuple <Foo,Foo> f(Foo && x,Foo && y) {
std::cout << "Called f" << std::endl;
return std::make_tuple <Foo,Foo> (std::move(x),std::move(y));
}
std::tuple <Foo,Foo> g(Foo && x,Foo && y) {
std::cout << "Called g" << std::endl;
return std::make_tuple <Foo,Foo> (std::move(x),std::move(y));
}
int main() {
Foo x,y;
std::tie(x,y) = f(std::move(x),std::move(y));
std::cout << "Finished with f(foo)" << std::endl;
std::tie(x,y) = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(foo))" << std::endl;
}
This generates
constructor
constructor
Called f
move
move
move assignment
move assignment
destructor
destructor
Finished with f(foo)
Called f
move
move
Called g
move
move
move assignment
move assignment
destructor
destructor
destructor
destructor
Finished with g(f(foo))
destructor
destructor
Basically, the same issue as above occurs: We get move assignments that would be nice if they disappeared.
Edit 2
Per #MooingDuck's suggestion, it's actually possible to return an rref from the functions. Generally, this would be a really bad idea, but since the memory is allocated outside of the function, it becomes a non-issue. Then, the number of moves is dramatically reduced. Unfortunately, if someone tries to assign the result to an rref, this will cause undefined behavior. All of the code and results are below.
For the single argument case:
#include <iostream>
struct Foo {
// Add some data to see if it gets moved correctly
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
data = x.data;
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
data = x.data;
std::cout << "copy assignment" << std::endl;
return *this;
}
};
Foo && f(Foo && foo) {
std::cout << "Called f: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
Foo && g(Foo && foo) {
std::cout << "Called g: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
int main() {
Foo foo(5);
foo = f(std::move(foo));
std::cout << "Finished with f(foo)" << std::endl;
foo = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo))" << std::endl;
Foo foo2 = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo)) a second time" << std::endl;
std::cout << "foo2.data = " << foo2.data << std::endl;
// Now, break it.
Foo && foo3 = g(f(Foo(4)));
// Notice that the destuctor for Foo(4) occurs before the following line.
// That means that foo3 points at destructed memory.
std::cout << "foo3.data = " << foo3.data << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
This generates
constructor
Called f: foo.data = 5
move assignment
Finished with f(foo)
Called f: foo.data = 5
Called g: foo.data = 5
move assignment
Finished with g(f(foo))
Called f: foo.data = 5
Called g: foo.data = 5
move
Finished with g(f(foo)) a second time
foo2.data = 5
constructor
Called f: foo.data = 4
Called g: foo.data = 4
destructor
foo3.data = 4. If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
In the multi-argument case
#include <tuple>
#include <iostream>
#include <utility>
// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}
// Now, for our example
struct Foo {
// Add some data to see if it gets moved correctly
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
std::tuple <Foo&&,Foo&&> f(Foo && x,Foo && y) {
std::cout << "Called f: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
std::tuple <Foo&&,Foo&&> g(Foo && x,Foo && y) {
std::cout << "Called g: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
int main() {
Foo x(5),y(6);
std::tie(x,y) = f(std::move(x),std::move(y));
std::cout << "Finished with f(x,y)" << std::endl;
std::tie(x,y) = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y))" << std::endl;
std::tuple <Foo,Foo> x_y = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y)) a second time" << std::endl;
std::cout << "(x.data,y.data) = (" << std::get <0>(x_y).data << ',' <<
std::get <1> (x_y).data << ')' << std::endl;
// Now, break it.
std::tuple <Foo&&,Foo&&> x_y2 = apply(g,f(Foo(7),Foo(8)));
// Notice that the destuctors for Foo(7) and Foo(8) occur before the
// following line. That means that x_y2points at destructed memory.
std::cout << "(x2.data,y2.data) = (" << std::get <0>(x_y2).data << ',' <<
std::get <1> (x_y2).data << ')' << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
This generates
constructor
constructor
Called f: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with f(x,y)
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with g(f(x,y))
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move
move
Finished with g(f(x,y)) a second time
(x.data,y.data) = (5,6)
constructor
constructor
Called f: (x.data,y.data) = (7,8)
Called g: (x.data,y.data) = (7,8)
destructor
destructor
(x2.data,y2.data) = (7,8). If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
destructor
destructor
It seems to me that what you need is not a mechanism to move back and forth a value through a function call, since references do that adequately, but a device to compose functions working that way.
template <void f(Foo &), void g(Foo &)>
void compose2(Foo &v){
f(v);
g(v);
}
Of course, you could make this more generic on the parameter type.
template <typename T, void f(T&), void (...G)(T&)>
void compose(T &v){
f(v);
compose2<T,G...>(v);
}
template <typename T>
void compose(Foo &){
}
Example:
#include <iostream>
//... above template definitions for compose elided
struct Foo {
int x;
};
void f(Foo &v){
v.x++;
}
void g(Foo &v){
v.x *= 2;
}
int main(){
Foo v = { 9 };
compose<Foo, f, g, f, g>(v);
std::cout << v.x << "\n"; // output "42"
}
Note that you could even parameterize the template on the procedure prototype, but at this time on my machine, only clang++ (v3.5) seems to accept it, g++ (4.9.1) doesn't like it.
You can do it without move if you use a little bit of indirection and compiler optimizations:
void do_f(Foo & foo); // The code that used to in in f
inline Foo f(Foo foo)
{
do_f(foo);
return foo; // This return will be optimized away due to inlining
}
Per #MooingDuck's suggestion, it's actually possible to return an rref from the functions. Generally, this would be a really bad idea, but since the memory is allocated outside of the function, it becomes a non-issue. Then, the number of moves is dramatically reduced. Unfortunately, if someone tries to assign the result to an rref, this will cause undefined behavior. All of the code and results are below.
For the single argument case:
#include <iostream>
struct Foo {
// Add some data to see if it gets moved correctly
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
data = x.data;
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
data = x.data;
std::cout << "copy assignment" << std::endl;
return *this;
}
};
Foo && f(Foo && foo) {
std::cout << "Called f: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
Foo && g(Foo && foo) {
std::cout << "Called g: foo.data = " << foo.data << std::endl;
return std::move(foo);
}
int main() {
Foo foo(5);
foo = f(std::move(foo));
std::cout << "Finished with f(foo)" << std::endl;
foo = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo))" << std::endl;
Foo foo2 = g(f(std::move(foo)));
std::cout << "Finished with g(f(foo)) a second time" << std::endl;
std::cout << "foo2.data = " << foo2.data << std::endl;
// Now, break it.
Foo && foo3 = g(f(Foo(4)));
// Notice that the destuctor for Foo(4) occurs before the following line.
// That means that foo3 points at destructed memory.
std::cout << "foo3.data = " << foo3.data << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
This generates
constructor
Called f: foo.data = 5
move assignment
Finished with f(foo)
Called f: foo.data = 5
Called g: foo.data = 5
move assignment
Finished with g(f(foo))
Called f: foo.data = 5
Called g: foo.data = 5
move
Finished with g(f(foo)) a second time
foo2.data = 5
constructor
Called f: foo.data = 4
Called g: foo.data = 4
destructor
foo3.data = 4. If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
In the multi-argument case
#include <tuple>
#include <iostream>
#include <utility>
// This comes from the N3802 proposal for C++
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>) {
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices =
std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>;
return apply_impl(std::forward<F>(f), std::forward<Tuple>(t), Indices{});
}
// Now, for our example
struct Foo {
// Add some data to see if it gets moved correctly
int data;
Foo() : data(0) {
std::cout << "default constructor" << std::endl;
}
Foo(int const & data_) : data(data_) {
std::cout << "constructor" << std::endl;
}
Foo(Foo && x) {
data = x.data;
std::cout << "move" << std::endl;
}
Foo(Foo const & x) {
data = x.data;
std::cout << "copy" << std::endl;
}
~Foo() {
std::cout << "destructor" << std::endl;
}
Foo & operator = (Foo && x) {
std::cout << "move assignment" << std::endl;
return *this;
}
Foo & operator = (Foo & x) {
std::cout << "copy assignment" << std::endl;
return *this;
}
};
std::tuple <Foo&&,Foo&&> f(Foo && x,Foo && y) {
std::cout << "Called f: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
std::tuple <Foo&&,Foo&&> g(Foo && x,Foo && y) {
std::cout << "Called g: (x.data,y.data) = (" << x.data << ',' <<
y.data << ')' << std::endl;
return std::tuple <Foo&&,Foo&&> (std::move(x),std::move(y));
}
int main() {
Foo x(5),y(6);
std::tie(x,y) = f(std::move(x),std::move(y));
std::cout << "Finished with f(x,y)" << std::endl;
std::tie(x,y) = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y))" << std::endl;
std::tuple <Foo,Foo> x_y = apply(g,f(std::move(x),std::move(y)));
std::cout << "Finished with g(f(x,y)) a second time" << std::endl;
std::cout << "(x.data,y.data) = (" << std::get <0>(x_y).data << ',' <<
std::get <1> (x_y).data << ')' << std::endl;
// Now, break it.
std::tuple <Foo&&,Foo&&> x_y2 = apply(g,f(Foo(7),Foo(8)));
// Notice that the destuctors for Foo(7) and Foo(8) occur before the
// following line. That means that x_y2points at destructed memory.
std::cout << "(x2.data,y2.data) = (" << std::get <0>(x_y2).data << ',' <<
std::get <1> (x_y2).data << ')' << ". If there's a destructor"
" before this line that'd mean that this reference is invalid."
<< std::endl;
}
This generates
constructor
constructor
Called f: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with f(x,y)
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move assignment
move assignment
Finished with g(f(x,y))
Called f: (x.data,y.data) = (5,6)
Called g: (x.data,y.data) = (5,6)
move
move
Finished with g(f(x,y)) a second time
(x.data,y.data) = (5,6)
constructor
constructor
Called f: (x.data,y.data) = (7,8)
Called g: (x.data,y.data) = (7,8)
destructor
destructor
(x2.data,y2.data) = (7,8). If there's a destructor before this line that'd mean that this reference is invalid.
destructor
destructor
destructor
destructor

Object returned from function and copy constructor

There is such code:
#include <iostream>
class A {
public:
int a;
A() : a(0) {
std::cout << "Default constructor" << " " << this << std::endl;
}
A(int a_) : a(a_) {
std::cout << "Constructor with param " << a_ << " " << this << std::endl;
}
A(const A& b) {
a = b.a;
std::cout << "Copy constructor " << b.a << " to " << a << " " << &b << " -> " << this << std::endl;
}
A& operator=(const A& b) {
a=b.a;
std::cout << "Assignment operator " << b.a << " to " << a << " " << &b << " -> " << this << std::endl;
}
~A() {
std::cout << "Destructor for " << a << " " << this << std::endl;
}
void show(){
std::cout << "This is: " << this << std::endl;
}
};
A fun(){
A temp(3);
temp.show();
return temp;
}
int main() {
{
A ob = fun();
ob.show();
}
return 0;
}
Result:
Constructor with param 3 0xbfee79dc
This is: 0xbfee79dc
This is: 0xbfee79dc
Destructor for 3 0xbfee79dc
Object ob is initialized by function fun(). Why copy constructor is not called there? I thought that when function returns by value then copy constructor or assignment operator is called. It seems that object constructed in function fun() is not destroyed after execution of function. How can be copy constructor forced to invoke in this case?
This was compiled by g++.
Why copy constructor is not called there?
RVO
How can be copy constructor forced to invoke in this case?
Pass an option to the compiler. For gcc, it is --no-elide-constructors option to disable the RVO
That is called Named Return Value Optimization and copy elision, and basically means that the compiler has figured out that the copy can be avoided by carefully placing the temporary and the object in the same memory location.
By default there would be three objects in that piece of code, temp inside fun, the return value and ob inside main, and as many as two copies, but by carefully placing temp in the same memory location as the returned object inside fun and placing ob in the same memory address the two copies can be optimized away.
I wrote about those two optimizations with a couple of pictures to explain what is going on here:
NRVO
Copy elision