正确地从临时对象的数据成员移动
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
,然后可以直接以相同的方式使用它。
考虑以下 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
,然后可以直接以相同的方式使用它。