class 标记为 && 的成员函数有哪些用例?

What are the use cases of class member functions marked &&?

我不知道哪个 C++ 标准提供了此功能,但我想不出任何用例。

A member functions with && modifier will only be considered by overload resolution if the object is rvalue

struct Foo 
{
    auto func() && {...}
};

auto a = Foo{};
a.func();            // Does not compile, 'a' is not an rvalue
std::move(a).func(); // Compiles
Foo{}.func();        // Compiles

有人可以解释一下这个的用例吗?
为什么我们希望某些例程只对右值执行?

中添加了 Ref-qualification。通常,值类别的传播对于泛型编程非常有用!

在语义上,函数的引用限定有助于传达意图;作用于左值引用或右值——类似于函数的 constvolatile 限定。这些也与传播限定符和类别的结构成员的行为相似。

实践中的一个很好的例子是 std::optional, which provides the std::optional::value() 函数,它在提取时将值类别传播到引用:

auto x = std::move(opt).value(); // retrieves a T&&

这类似于 structs 的成员访问,其中值类别在访问时传播:

struct Data {
    std::string value;
};

auto data = Data{};

auto string = std::move(data).value; // expression yields a std::string&&

就通用组合而言,这极大地简化了输入可能是左值或右值的情况。例如,考虑使用转发引用的情况:

// Gets the internal value from 'optional'
template <typename Optional>
auto call(Optional&& opt) {
    // will be lvalue or rvalue depending on what 'opt' resolves as
    return std::forward<Optional>(opt).value(); 
}

没有 ref-qualification,完成上述代码的唯一方法是创建两个静态分支——使用 if constexpr 或 tag-dispatch,或其他一些方法。类似于:

template <typename Optional>
auto call(Optional&& opt) {
    if constexpr (std::is_lvalue_reference_v<Optional>) {
        return opt.value(); 
    } else {
        return std::move(opt.value()); 
    }
}

在技术层面上,函数的右值限定提供了使用移动构造优化代码并以语义清晰的方式避免复制的机会。

很像当您在某个值上看到 std::move(x) 时,您会认为 x 即将过期;期望 std::move(x).get_something() 会导致 x 做同样的事情并非不合理。

如果将 && 重载与 const & 重载组合,则可以在 API 中表示不可变复制和变异移动。以不起眼的“Builder”模式为例。通常,Builder 模式对象持有数据片段,这些数据片段将在构建时被馈送到对象中。这需要在构造过程中进行副本,无论是浅副本还是深副本。对于 large 对象,这可能会非常昂贵:

class Builder {
private:
  
    // Will be copied during construction
    expensive_data m_expensive_state;
    ...

public:

    auto add_expensive_data(...) -> Builder&;
    auto add_other_data(...) -> Builder&;
    ...

    auto build() && -> ExpensiveObject {
        // Move the expensive-state, which is cheaper.
        return ExpensiveObject{std::move(m_expensive_state), ...}
    }
    auto build() const & -> ExpensiveObject
        // Copies the expensive-state, whcih is costly
        return ExpensiveObject{m_expensive_state, ...}
    }
    ...
};

没有右值限定,您将被迫在实现上做出选择:

  1. 在非 const 函数中执行破坏性操作,例如移动,并记录安全性(并希望 API 没有被调用错误),或
  2. 为了安全起见,只复制所有内容

通过右值限定,它成为调用者的可选功能,并且从编写的代码中可以清楚地看出其意图是什么——无需文档:

// Uses the data from 'builder'. May be costly and involves copies
auto inefficient = builder.build();

// Consumes the data from 'builder', but makes 'efficient's construction
// more efficient.
auto efficient = std::move(builder).build();

作为一个额外的好处,静态分析通常可以检测到移动后使用的情况,因此在 std::move 之后意外使用 builder 可以比简单的文档更好地发现。

What are the use cases of class member functions marked &&?

一个用例是“getter”风格的函数。他们传统上 return 引用子对象或拥有的对象,其生命周期与超级对象相关联。

然而,这有时是有问题的,因为在右值上调用 getter 将导致对将在表达式末尾销毁的对象的引用。那是不安全的:

struct demo {
    int& get_mem() { return mem; }
private:
    int mem;
};

demo get_demo();

int& ref = get_demo().get_mem();
std::cout << ref; // bug

左值引用限定符可防止此类错误:

int& get_mem() & { return mem; }
int& ref = get_demo().get_mem(); // safely ill-formed

但它也阻止了以前正确的代码的工作,例如:

std::cout << get_demo().get_mem(); // used to be OK; now ill-formed

一个解决方案是提供两个重载,每个重载都有不同的限定符:

int& get_mem() &  { return mem; }
int  get_mem() && { return mem; }

它通常与包装其他类型的类型一起使用,具有模拟所有 constess 和值类别的全套重载:

const int&  get_mem() const& ;
      int&  get_mem()      & ;
const int&& get_mem() const&&;
      int&& get_mem()      &&;