Home > front end >  Correctly move from a data member of a temporary object
Correctly move from a data member of a temporary object

Time:02-02

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?

CodePudding user response:

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.

  •  Tags:  
  • Related