我遇到了执行此操作的代码
SomeObject parse (std::istream && input) {....
input
参数是一个右值引用,通常意味着该函数旨在获取该参数的所有权。这不完全是这里发生的事情。
parse
函数将完全消耗输入流,并且需要右值引用,因为随后调用代码将放弃对istream
的所有权,因此这表明输入流将被无法使用。
我认为这是可以的,因为parse
函数实际上并没有移动对象,因此没有切出子类型的危险。从parse
的角度来看,这基本上是一种正常的参考,只有对调用函数有一种可编译的注释,您必须放弃对该流的所有权。
此代码实际上安全吗?还是有一些被忽视的微妙之处使之变得危险?
答案 0 :(得分:6)
An std::istream
isn't moveable,因此没有实际好处。
这已经预示着事物可能被“修改”,而没有暗示您正在转让std::istream
对象的所有权(您不做也不可以做)的困惑。
我可以种类看到使用此逻辑背后的原理,即说流在逻辑上被移动了,但是我认为您必须对“该物的所有权正在转移”加以区分,和“我保留此东西的所有权,但我会让您使用它的所有服务”。所有权转让作为C ++中的一个约定已广为人知,而事实并非如此。您的代码用户在必须编写parse(std::move(std::cin))
时会怎么想?
您的方式并非“危险”;您将无法使用左值引用执行任何操作,而不能使用左值引用进行操作。
仅仅引用左值引用会更加自我记录和常规。
答案 1 :(得分:5)
std::move
仅从对象生成右值引用,仅此而已。右值的性质使得您可以假定在完成后,没有其他人会关心它的状态。 std::move
然后用于允许开发人员对具有其他值类别的对象做出承诺。换句话说,在有意义的上下文中调用std::move
等效于说“我保证不再关心该对象的状态”。
因为您将使该对象基本上不可用,并且您想确保调用者不再使用该对象,所以使用右值引用会在某种程度上强制执行此期望。它迫使调用者对您的功能做出承诺。未能兑现承诺将导致编译器错误(假设没有其他有效的重载)。不管您是否真的离开了对象,都没有关系,只是原始所有者已同意放弃其所有权。
答案 2 :(得分:2)
在当前的std::istream
界面下,您尝试在此处进行的操作不是“危险”的,在这种情况下,似乎没有任何情况可能会导致右值引用采取左值引用时没有未定义的行为。但是恕我直言,这整个装置的语义充其量是非常可疑的。调用代码“放弃所有权”却同时“不转让”是什么意思? parse()
返回后,谁“拥有”该流! parse()
究竟以什么方式使流“不可用”?如果在“消耗”整个流之前由于某些错误导致解析失败怎么办?那么,流“不可用”吗?不允许任何人尝试阅读其余内容吗?在这种情况下,“所有权”是否以某种方式“返还”了调用代码?
流是一个抽象概念。流抽象的目的是充当一个接口,通过该接口,某人可以使用输入而不必知道数据来自何处,生活或如何访问和管理。如果parse()
的目的是解析来自任意来源的输入,则它不应与来源的性质有关。如果它与源的性质有关,则应请求一种特定类型的源。恕我直言,这就是您的界面与之矛盾的地方。当前,parse()
采用任意来源。该界面说:我会接受您提供给我的任何信息,我不在乎它是如何实现的。只要是流,我就可以使用它。同时,它要求调用者放弃实际实现该流的对象。接口要求调用者交出某些东西,接口本身阻止该接口背后的任何实现以任何方式知道,访问或使用。例如,如何从parse()
中读取std::ifstream
?谁随后关闭文件?如果不能是解析器。也不能是我,因为调用解析器迫使我移交对象。同时,我知道解析器甚至无法知道必须关闭我移交的文件…
最后它仍然会做正确的事情,因为接口的实现无法实际完成接口建议的操作,因此我的std::ifstream
析构函数将运行并关闭文件。但是,我们这样那样说谎对我们有什么好处呢?您答应了永远不要接管的对象,我答应永远知道我将永远不再碰触对象……
答案 3 :(得分:2)
您的假设rvalue参考参数暗示“拥有所有权”是完全错误的。右值引用只是一种特定的引用,它带有自己的初始化规则和重载解决规则。不多不少。形式上,它与所引用对象的“移动”或“取得所有权”没有特殊的相似性。
的确,对 move语义的支持被认为是右值引用的主要目的之一,但是您仍然不应假定这只是其 的目的,并且这些功能是不可分割的。就像任何其他语言功能一样,它可能会允许大量成熟的替代惯用用法。
与您刚才引用的报价类似的示例报价实际上存在于标准库中。这是C ++ 11(和C ++ 17,取决于某些细微差别)中引入的额外重载
template< class CharT, class Traits, class T >
basic_ostream< CharT, Traits >& operator<<( basic_ostream<CharT,Traits>&& os,
const T& value );
template< class CharT, class Traits, class T >
basic_istream<CharT,Traits>& operator>>( basic_istream<CharT,Traits>&& st, T&& value );
它们的主要目的是“消除” operator <<
的成员和非成员重载之间的行为差异
#include <string>
#include <sstream>
int main()
{
std::string s;
int a;
std::istringstream("123 456") >> a >> s;
std::istringstream("123 456") >> s >> a;
// Despite the obvious similarity, the first line is well-formed in C++03
// while the second isn't. Both lines are well-formed in C++11
}
它利用了右值引用可以绑定到临时对象,并且仍然将它们视为可修改对象的事实。在这种情况下,右值引用用于与移动语义无关的目的。这完全正常。