这是无效的吗?gcc接受它,clang和msvc不接受。
#include <memory>
class Holder {
std::unique_ptr<int> data;
public:
operator std::unique_ptr<int>() && { return std::move(data); }
};
std::unique_ptr<int> test()
{
Holder val;
return val;
}
假设我不想添加std::unique_ptr<int> Holder::TakeData() { return std::move(data); }
,我唯一能想到的其他解决方法就是移动返回值:
std::unique_ptr<int> test()
{
Holder val;
return std::move(val); // lets the conversion proceed
}
但是后来gcc 9.3+告诉我那个std::move
是多余的(启用了所有警告)。WTF?我的意思是,是的,gcc
不需要转移,但是那时没有其他人接受该代码。如果不是gcc,那么某些人不可避免地会在以后遇到问题。
TakeData
功能并使用它?更糟糕的是-我是否应该使TakeData
函数仅限于右值上下文,即必须这样做return std::move(val).TakeData()
?添加operator std::unique_ptr<int>() & { return std::move(data); }
不是一种选择,因为它显然会导致令人讨厌的错误-可以在错误的上下文中调用它。
标准要求“隐式”右值转换。但是根据您使用的是哪个标准版本,哪个“正确”的编译器会有所不同。
在C ++ 17中
[class.copy.elision](重点是我的)
3在以下复制初始化上下文中,可以使用移动操作代替复制操作:
如果return语句中的表达式是一个(可能带括号的)id表达式,该对象使用在最内层的封闭函数或lambda表达式的主体或参数声明子句中声明的具有自动存储期限的对象进行命名,或者
...
首先执行重载决议为副本选择构造函数,就好像该对象是由右值指定的一样。如果第一个重载解析失败或没有执行,或者所选构造函数的第一个参数的类型不是对该对象类型的右值引用(可能是cv限定的),则再次执行重载解析,将对象视为左值。[注意:无论是否出现复制省略,都必须执行此两阶段重载解决方案。如果不执行省略操作,它将确定要调用的构造函数,并且即使取消了调用,所选的构造函数也必须可访问。—尾注]
在C ++ 17之前,GCC是错误的。使用val
隐式的右值应该初始化失败是考虑粗体字标识句话我的返回类型(在右值参考unique_ptr
c'tor不绑定直接val
)。但是来到C ++ 20时,该句子不再存在。
C ++ 20
3隐式可移动实体是自动存储持续时间的变量,它是非易失性对象或对非易失性对象类型的右值引用。在以下复制初始化上下文中,可以使用移动操作代替复制操作:
如果return([stmt.return])或co_return([stmt.return.coroutine])语句中的表达式是一个(可能带括号的)id表达式,该表达式命名了在主体或参数声明子句中声明的隐式可移动实体最内层的封闭函数或lambda表达式,或
[...]
选择要复制的构造函数或return_value的重载解析首先要执行,就好像表达式或操作数是一个右值一样。如果第一个重载解析失败或没有执行,则将表达式或操作数视为左值,再次执行重载解析。[注意:无论是否出现复制省略,都必须执行此两阶段重载解决方案。如果不执行省略操作,它将确定要调用的构造函数或return_value重载,并且即使取消了调用,所选的构造函数或return_value重载也必须可访问。—尾注]
因此,代码的正确性取决于编译器的时间旅行属性。
至于应该如何编写这样的代码。如果无法获得一致的结果,则可以选择使用函数的确切返回类型
std::unique_ptr<int> test()
{
Holder val;
std::unique_ptr<int> ret_val = std::move(val);
return ret_val;
}
我从一开始就同意,这可能看起来不像简单地返回就那么吸引人val
,但至少在NRVO中它表现得很好。因此,我们获得的副本的可能性不会unique_ptr
超过我们最初想要的副本。
如果这仍然不太吸引人,那么我发现您最喜欢的是窃取成员函数的想法。但没有占味。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句