复制消除允许实现即使在复制构造或移动构造具有可观察副作用的情况下,也省略复制构造或移动构造。C++17之前,所有复制消除情形都被允许,但不是强制的。C++17改变了prvalue的语义,使此前属于临时对象消除的若干情形改为直接构造。
返回的prvalue与命名局部变量 #
自C++17起,从函数返回函数返回类型的prvalue时,会直接构造结果对象。因此,复制构造函数和移动构造函数即使被删除,也不会阻止这种形式;不过,在return语句处,返回类型的析构函数仍必须可访问且未被删除:
cpp
struct Token {
Token();
Token(const Token&) = delete;
Token(Token&&) = delete;
};
Token make_token() {
return Token{}; // 自C++17起保证直接构造
}具名返回值优化(NRVO)仍然是可选的,包括C++17及更高版本:
cpp
struct Widget {};
Widget make_named_widget() {
Widget widget;
return widget; // 可能应用NRVO;需要可行的复制或移动操作
}当NRVO不适用时,return语句中的符合条件的局部变量会在重载决议中被视为右值。不要仅为了请求这种行为而写 return std::move(token);。这样做可能抑制NRVO,并将表达式从符合NRVO条件的名称改变为xvalue。应直接返回命名局部变量。
接口与生命周期设计 #
当调用方接收独立结果时,优先返回已完成的值:
cpp
#include <string>
std::string make_label(int id) {
return "item-" + std::to_string(id);
}返回值不是对局部对象的引用。只有在被引用对象的生命周期长于调用,并且借用或别名访问是预期契约时,返回引用才是正确的:
cpp
const Config& active_config(); // 引用生命周期更长的存储
绝不要返回对自动局部变量的左值引用或右值引用。右值引用不会将局部对象的生命周期延长到return之后;它只传达引用类别。相反,将临时对象绑定到局部 const T& 或 T&& 会将该临时对象的生命周期延长到该局部引用的生命周期,但不会延长到函数返回的引用的生命周期。
编写按值返回的接口时,不要使用防御性移动;只有在证明存在成本后再进行测量。C++17的保证适用于规定的prvalue情形;NRVO、基类子对象初始化以及向不同结果类型的转换仍是独立情形,各自具有相应的复制或移动要求。
评论
在 GitHub 上阅读或评论(发表评论需要登录)