cannot understand why this rvalue reference segfault

90 views
Skip to first unread message

Yubin Ruan

unread,
Jan 17, 2018, 10:51:58 AM1/17/18
to std-dis...@isocpp.org
Hi,

I think constructing an object from a to-be-deallocated one is OK as long as
we don't use any reference to that to-be-deallocated object after it has been
deallocated. But mysteriously this one fail:

#include <iostream>
#include <algorithm>
#include <utility>
#include <cstdlib>
#include <cstring>

using namespace std;

class Apple {
public:
char arr[1000];

Apple() {
arr[0] = 1;
}

Apple(const Apple& app) {
std::cout << "lref version called\n";
memcpy(arr, app.arr, 1000);
}

Apple(Apple&& app) {
std::cout << "rref version called\n";
memcpy(arr, app.arr, 1000);
}
};

// function that return a lvalue-reference
const Apple& func_lref() {
Apple app;
// do many things with app here
return app;
}

// function that return a rvalue reference
Apple&& func_rref() {
Apple app;
//...
return std::move(app);
}

int main() {
Apple app1 = func_lref();

// segfault here
Apple app2 = func_rref();
}

Why would this happen? Isn't constructing a std::vector<T> using a move
constructor assumed to be the advantage of std::move() ?

vector<T> func() {
vector<T> vec;
// ...
return vec;
}

// should construct `somevec' using a move constructor such that it
// would me more efficient
vector<T> somevec = func();

What do I miss here?

Yubin

Todd Fleming

unread,
Jan 17, 2018, 12:09:39 PM1/17/18
to ISO C++ Standard - Discussion
On Wednesday, January 17, 2018 at 10:51:58 AM UTC-5, Yubin Ruan wrote:

What do I miss here?

        Yubin

Both func_lref() and func_rref() return references to objects that are destroyed at function exit. func() returns a vector by value.

Todd

Yubin Ruan

unread,
Jan 17, 2018, 8:04:43 PM1/17/18
to std-dis...@isocpp.org
How about this one:

vector<T> func() {
vector<T> vec;
// ...
return vec;
}

// This should construct `somevec' using a move constructor such that it
// would me more efficient
vector<T> somevec = func();

I am also returning a to-be-destroyed object, but it a move constructor is
still invoked.

Yubin

Todd Fleming

unread,
Jan 17, 2018, 8:11:58 PM1/17/18
to ISO C++ Standard - Discussion
On Wednesday, January 17, 2018 at 8:04:43 PM UTC-5, Yubin Ruan wrote:
> Both func_lref() and func_rref() return references to objects that are
> destroyed at function exit. func() returns a vector by value.

How about this one:

    vector<T> func() {
        vector<T> vec;
        // ...
        return vec;
    }

    // This should construct `somevec' using a move constructor such that it
    // would me more efficient
    vector<T> somevec = func();

I am also returning a to-be-destroyed object, but it a move constructor is
still invoked.

        Yubin

You're returning the vector by value. You're not returning a reference to the vector. These are UB:

    vector<T>& bad_vector_func_1() {
        vector
<T> vec;
       
// ...
       
return vec;
   
}

    vector
<T>&& bad_vector_func_1() {
        vector
<T> vec;
       
// ...
       
return move(vec);
   
}


Todd

Yubin Ruan

unread,
Jan 17, 2018, 8:19:17 PM1/17/18
to std-dis...@isocpp.org
On Thu, Jan 18, 2018 at 9:11 AM, Todd Fleming <tbfl...@gmail.com> wrote:
> On Wednesday, January 17, 2018 at 8:04:43 PM UTC-5, Yubin Ruan wrote:
>>
>> > Both func_lref() and func_rref() return references to objects that are
>> > destroyed at function exit. func() returns a vector by value.
>>
>> How about this one:
>>
>> vector<T> func() {
>> vector<T> vec;
>> // ...
>> return vec;
>> }
>>
>> // This should construct `somevec' using a move constructor such that
>> it
>> // would me more efficient
>> vector<T> somevec = func();
>>
>> I am also returning a to-be-destroyed object, but it a move constructor is
>> still invoked.
>>
>> Yubin
>
>
> You're returning the vector by value. You're not returning a reference to
> the vector.

I remember the standard says that the compiler should prefer/use a
move constructor for the return value, that is, even though `vec' is
returned-by-value, the compiler should invoke a move constructor for
the expression. At least that is true for std::unique_ptr<T>:

std::unique_ptr<T> func() {
std::unique_ptr<T> ptr = ...;
// ...

// although returned-by-value, this will act as if a
std::move() has been called upon it
return ptr;
}

Isn't this also true for vector?

Yubin

Todd Fleming

unread,
Jan 17, 2018, 8:23:12 PM1/17/18
to ISO C++ Standard - Discussion
We should move this conversation to another forum. Forget move semantics for a moment and tell me what's wrong with this function:

int& f() {
   
return 5;
}



Todd
 

Yubin Ruan

unread,
Jan 17, 2018, 8:33:18 PM1/17/18
to std-dis...@isocpp.org
I know what you mean. I just to can't understand the point of the move
semantic, therefore I want to see its difference with a canonical copy
constructor (that is, using a T& rather than T&&). Please don't bother
with the contrived example above.

Yubin

Todd Fleming

unread,
Jan 17, 2018, 8:42:54 PM1/17/18
to ISO C++ Standard - Discussion
On Wednesday, January 17, 2018 at 8:33:18 PM UTC-5, Yubin Ruan wrote:
> We should move this conversation to another forum. Forget move semantics for
> a moment and tell me what's wrong with this function:
>
> int& f() {
>     return 5;
> }

I know what you mean. I just to can't understand the point of the move
semantic, therefore I want to see its difference with a canonical copy
constructor (that is, using a T& rather than T&&). Please don't bother
with the contrived example above.

        Yubin

Your func_lref() and func_rref() functions make the same mistake as that function. Rewrite them to return non-references, then you'll see the compiler invoke your T&& constructors correctly.

Todd

Todd Fleming

unread,
Jan 17, 2018, 8:46:21 PM1/17/18
to ISO C++ Standard - Discussion
Correction: then you might see the compiler invoke your T&& constructors. c++11 - c++14 allow the compiler to use RVO instead. C++17 requires compilers use something similar to RVO. Either way, constructors and destructor calls get bypassed.

Todd

Yubin Ruan

unread,
Jan 17, 2018, 9:18:57 PM1/17/18
to std-dis...@isocpp.org
On Wed, Jan 17, 2018 at 05:46:21PM -0800, Todd Fleming wrote:
> On Wednesday, January 17, 2018 at 8:42:54 PM UTC-5, Todd Fleming wrote:
> >
> > On Wednesday, January 17, 2018 at 8:33:18 PM UTC-5, Yubin Ruan wrote:
> >>
> >> > We should move this conversation to another forum. Forget move
> >> semantics for
> >> > a moment and tell me what's wrong with this function:
> >> >
> >> > int& f() {
> >> > return 5;
> >> > }
> >>
> >> I know what you mean. I just to can't understand the point of the move
> >> semantic, therefore I want to see its difference with a canonical copy
> >> constructor (that is, using a T& rather than T&&). Please don't bother
> >> with the contrived example above.
> >>
> >> Yubin
> >>
> >
> > Your func_lref() and func_rref() functions make the same mistake as that
> > function. Rewrite them to return non-references, then you'll see the
> > compiler invoke your T&& constructors correctly.
> >
> > Todd
> >
>
> Correction: then you *might* see the compiler invoke your T&& constructors.
> c++11 - c++14 allow the compiler to use RVO instead. C++17 requires
> compilers use something similar to RVO. Either way, constructors and
> destructor calls get bypassed.

OK I notice this: using "-fno-elide-constructors" will prohibit GCC from
optimizing away the constructors.

Now turn to another topic. As the subject indicates, I cannot see the point of
move constructors when we already have copy constructor. For me, both move
constructors and copy constructors *have* to do copying:

#include <iostream>
#include <algorithm>
#include <utility>
#include <cstdlib>
#include <cstring>

using namespace std;

class Apple {
public:
char arr[1000];

Apple() {
arr[0] = 1;
}

Apple(const Apple& app) {
std::cout << "lref version called\n";
memcpy(arr, app.arr, 1000);
}

Apple(Apple&& app) {
std::cout << "rref version called\n";
memcpy(arr, app.arr, 1000);
}
};

Apple func_rref() {
Apple app;
//...
return app;
}

int main() {
Apple app0;
// copy-constructor invoked, which involves a memcpy
Apple app1 = app0;

// move-constructor invoked, which also involves a memcpy
Apple app2 = func_rref();
}

Some people claim that move semantic allows programmers to "move" instead of
doing a bloody copying which might incur huge overhead. From the claims I saw,
it is as if move semantic can silently bind an object to another name instead
of doing any internal data copying. However this is not true. As you can see
from the example above, we still have to copy the internal data.

So, so far for the move semantic, isn't it that what we have is another fancy
name for the same thing?

Yubin

Michael Kilburn

unread,
Jan 17, 2018, 9:23:14 PM1/17/18
to std-dis...@isocpp.org

> So, so far for the move semantic, isn't it that what we have is another fancy
> name for the same thing?

'Copy' is for making a copy without modifying original object. 'Move' is for making a copy potentially modifying original objecy. There is a large class of situations where latter performs much better.

Todd Fleming

unread,
Jan 17, 2018, 9:32:55 PM1/17/18
to ISO C++ Standard - Discussion
On Wednesday, January 17, 2018 at 9:18:57 PM UTC-5, Yubin Ruan wrote:
Now turn to another topic. As the subject indicates, I cannot see the point of
move constructors when we already have copy constructor. For me, both move
constructors and copy constructors *have* to do copying:

In your case, yes, there is no advantage.
 
Some people claim that move semantic allows programmers to "move" instead of
doing a bloody copying which might incur huge overhead. From the claims I saw,
it is as if move semantic can silently bind an object to another name instead
of doing any internal data copying. However this is not true. As you can see
from the example above, we still have to copy the internal data.

Modify Apple to see the difference:
* Make arr a pointer instead of an array
* Default constructor should allocate the array
* Destructor should deallocate the array
* Copy constructor should copy the array
* Move constructor should take the pointer away from the source; no copy needed. Make sure you don't share the pointer.

This will bring it closer to the way vector, string, and other types operate internally.
 
        Yubin

Todd

Yubin Ruan

unread,
Jan 17, 2018, 9:36:52 PM1/17/18
to std-dis...@isocpp.org
So the point here is that a copy constructor disallow modifying original
object while a move constructor allow that, right. From this perspective, why
would latter performs much better?

Yubin

Thiago Macieira

unread,
Jan 17, 2018, 9:40:14 PM1/17/18
to std-dis...@isocpp.org
On Thursday, 18 January 2018 02:18:50 PST Yubin Ruan wrote:
> Now turn to another topic. As the subject indicates, I cannot see the point
> of move constructors when we already have copy constructor. For me, both
> move constructors and copy constructors *have* to do copying:

Except that the move constructor does not always have to do copying.

In your case it does. Now use a pointer instead of a 1000-character array.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center



Yubin Ruan

unread,
Jan 17, 2018, 9:56:16 PM1/17/18
to std-dis...@isocpp.org
On Wed, Jan 17, 2018 at 06:32:54PM -0800, Todd Fleming wrote:
> On Wednesday, January 17, 2018 at 9:18:57 PM UTC-5, Yubin Ruan wrote:
> >
> > Now turn to another topic. As the subject indicates, I cannot see the
> > point of
> > move constructors when we already have copy constructor. For me, both move
> > constructors and copy constructors *have* to do copying:
> >
>
> In your case, yes, there is no advantage.
>
>
> > Some people claim that move semantic allows programmers to "move" instead
> > of
> > doing a bloody copying which might incur huge overhead. From the claims I
> > saw,
> > it is as if move semantic can silently bind an object to another name
> > instead
> > of doing any internal data copying. However this is not true. As you can
> > see
> > from the example above, we still have to copy the internal data.
> >
>
> Modify Apple to see the difference:
> * Make arr a pointer instead of an array
> * Default constructor should allocate the array
> * Destructor should deallocate the array
> * Copy constructor should copy the array
> * Move constructor should take the pointer away from the source; no copy
> needed. Make sure you don't share the pointer.

So the point here is that with a copy constructor we have to allocate a new
array (because we cannot share the pointer), however with a move constructor
we don't have to perform allocation (because the original object is useless
now and should be discarded after the move.

Now I can conclude:

All the performance gain from using a move constructor nail down to
pointers and operations related to those pointers (memory allocation, I/O
and then use pointers as a handle, etc.). Because moving a int is the same
as copying a int and the same for long, double and all other elementary
types, no performance boost can be gained if one have to do the same
copying in move constructors as in copy constructors. But because of the
move semantic, that is, the original object should no longer be used,
probably we can "steal" its internal pointer, which save us some time with
memory allocation or I/O, etc.

Or maybe there are other advantages (related to performance)?

Yubin

Michael Kilburn

unread,
Jan 17, 2018, 11:25:31 PM1/17/18
to std-dis...@isocpp.org
On Thu, Jan 18, 2018 at 4:36 AM, Yubin Ruan <ablack...@gmail.com> wrote:
On Wed, Jan 17, 2018 at 08:23:10PM -0600, Michael Kilburn wrote:
> 'Copy' is for making a copy without modifying original object. 'Move' is
> for making a copy potentially modifying original object. There is a large

> class of situations where latter performs much better.

So the point here is that a copy constructor disallow modifying original
object while a move constructor allow that, right. From this perspective, why
would latter performs much better?

I think answer is rather obvious... Deep copy vs shallow copy is one example.

--
Sincerely yours,
Michael.

Bo Persson

unread,
Jan 18, 2018, 7:37:05 AM1/18/18
to std-dis...@isocpp.org
If the copy constructor and move constructor do the same thing, like in
your example, there is no reason to have them both.

In some cases the move-constructor might be able to resuse resources
instead of copying them. That might improve performance.

In other cases, like std::unique_ptr and std::fstream, the object is
move-only (copying not allowed) becauses you only want to have one
instance. There it is about functionality and not performance.


Bo Persson

Yubin Ruan

unread,
Jan 20, 2018, 7:31:55 AM1/20/18
to std-dis...@isocpp.org
Yes, it is the semantic that matters.

Yubin
Reply all
Reply to author
Forward
0 new messages