C++11中添加了Ref-qualification。通常,值类别的传播对于泛型编程非常有用!
从语义上讲,函数上的ref-qualifications有助于传达意图;作用于左值引用或右值引用,这类似于函数的const
或volatile
限定符。 这些也与结构体成员的行为相平行,它们传播修饰符和类别。
实践中一个很好的例子是std::optional
,它提供了std::optional::value()
函数,该函数将值类别传播到在提取时的引用:
auto x = std::move(opt).value();
这类似于使用
struct
进行成员访问,其中值类别在访问时传播:
struct Data {
std::string value;
};
auto data = Data{};
auto string = std::move(data).value;
就一般组成而言,这大大简化了输入可能是lvalue或rvalue的情况。例如,考虑使用转发引用的情况:
template <typename Optional>
auto call(Optional&& opt) {
return std::forward<Optional>(opt).value();
}
如果没有ref-qualification,实现上述代码的唯一方法是创建两个静态分支——使用if constexpr
或标签分发,或其他方式。类似于:
template <typename Optional>
auto call(Optional&& opt) {
if constexpr (std::is_lvalue_reference_v<Optional>) {
return opt.value();
} else {
return std::move(opt.value());
}
}
在技术层面上,函数的rvalue限定符提供了优化代码移动构造和以语义明确的方式避免复制的机会。
就像当您看到一个
std::move(x)
时,您可以预期
x
即将过期;如果您期望
std::move(x).get_something()
会导致
x
做同样的事情,这并不是不合理的。
如果您将
&&
重载与
const &
重载结合起来,则可以在API中表示不可变复制和可变移动。例如,谦卑的“Builder”模式。通常,Builder模式对象保存将在构造时输入对象的数据片段。这需要在构建过程中进行复制,无论是浅复制还是深复制。对于
大对象,这可能非常昂贵:
class Builder {
private:
expensive_data m_expensive_state;
...
public:
auto add_expensive_data(...) -> Builder&;
auto add_other_data(...) -> Builder&;
...
auto build() && -> ExpensiveObject {
return ExpensiveObject{std::move(m_expensive_state), ...}
}
auto build() const & -> ExpensiveObject
return ExpensiveObject{m_expensive_state, ...}
}
...
};
没有rvalue限定符,你被迫在实现时做出选择:
1. 在非
const
函数中执行破坏性操作,只是记录安全性(并希望API不会被错误调用),或者
2. 为了安全起见,复制所有内容
使用rvalue限定符后,它成为调用者的可选功能,并且从编写的代码中清楚地表明意图--无需文档。
auto inefficient = builder.build();
auto efficient = std::move(builder).build();
作为额外的好处,静态分析通常可以检测到使用后移动的情况,因此比起简单的说明文档,可以更好地捕捉到在
std::move
后意外使用
builder
的情况。