正确地从临时对象的数据成员移动

Correctly move from a data member of a temporary object

考虑以下 C++ 代码

#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();
}

与输出

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
[...]

WrapMe 成员移动的正确方法是:喜欢 Wrapper1(return 按值)或喜欢 Wrapper2(return 按值右值引用)呢?或者正如输出所暗示的那样,这两种方式在这里是等价的吗?如果不是,为什么?

WrapMe&& data()&& { return std::move(member); }

这实际上并没有移动任何东西。它只是 return 对成员的右值引用。例如我可以做

auto&& wrapMe2 = Wrapper2().data();

现在 wrapMe2 将成为悬空引用或

auto w = wrapper2();
auto&& wrapMe2 = std::move(x).data();

现在我有一个对 w 成员的引用,w 根本没有改变。

只是因为调用了WrapMe的move构造函数来初始化原行的wrapMe2对象,才真正发生了move操作。


版本

WrapMe data()&& { return std::move(member); }

已经调用移动构造函数来构造 return 值。 returned 值永远不会引用原始对象或其成员。

auto wrapMe1 = Wrapper1().data();

然后再次调用移动构造函数从 .data() 的 return 值初始化 wrapMe1。然而,允许编译器省略这第二个移动构造函数调用,而是直接从 return 语句中的表达式构造 wrapMe1。这就是您看到相同结果的原因。

对于 C++17 或更高版本,此省略甚至是强制性的。


我不知道你的用例,所以我不能确定正确的方法是什么,只是猜测你想做什么:

为了两个重载之间的一致性,我会使用 reference-returning 版本。让 data 为左值而不是右值提供对成员的可修改访问,这会造成混淆。

但是,您实际上并不需要成员函数来执行此操作。通过 data 调用者可以完全控制成员,因此您可以从一开始就将成员设为 public,然后可以直接以相同的方式使用它。