0

考虑以下 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:like Wrapper1(return by value) 还是 like Wrapper2(return by rvalue-reference) 呢?或者,正如输出所暗示的那样,这两种方式在这里是等价的?如果不是,为什么?

4

1 回答 1

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

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

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

现在wrapMe2将是一个悬空的参考或

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

w现在我有一个没有改变的成员的引用w

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


版本

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

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

线

auto wrapMe1 = Wrapper1().data();

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

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


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

为了两个重载之间的一致性,我会使用返回引用的版本。为左值而不是右data值提供对成员的可修改访问会令人困惑。

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

于 2022-02-01T18:04:40.577 回答