提问人:spraff 提问时间:1/16/2019 更新时间:1/17/2019 访问量:770
将 std::istream&& 作为参数是否合理?
Is it reasonable to take std::istream&& as a argument?
问:
我遇到了这样做的代码:
SomeObject parse (std::istream && input) {....
参数是右值引用,这通常意味着函数旨在获取参数的所有权。这并不完全是这里发生的事情。input
该函数将完全消耗输入流,并且它需要右值引用,因为调用代码将放弃 的所有权,因此这是一个信号,表明输入流将不可用。parse
istream
我认为这没关系,因为由于该函数实际上不会移动对象,因此不存在切出子类型的危险。从的角度来看,这基本上是一个普通的引用,只是对调用函数有一种可编译的注释,你必须放弃流的所有权。parse
parse
这段代码真的安全吗?还是有一些被忽视的微妙之处使这变得危险?
答:
std::istream
不可移动,因此没有任何实际好处。
这已经表明该事物可能被“修改”,而不会暗示您正在转移对象的所有权(您没有这样做,也无法这样做)。std::istream
我可以看出用它来说明流在逻辑上被移动背后的基本原理,但我认为你必须区分“这个东西的所有权正在转移”和“我保留这个东西的所有权,但我会让你消费它的所有服务”。所有权转让在 C++ 中被很好地理解为一种约定,但事实并非如此。当你的代码的用户必须编写代码时,他们会怎么想?parse(std::move(std::cin))
不过,你的方式并不“危险”;您将无法对该右值引用执行任何操作,而对左值引用则无法执行任何操作。
只采用左值引用会更加自我记录和传统。
评论
rvalue
C++
std::istream
std::move
只是从对象生成右值引用,仅此而已。右值的本质是这样的,你可以假设在你完成它之后,没有人会关心它的状态。 然后用于允许开发人员对具有其他值类别的对象做出承诺。换句话说,在有意义的上下文中调用等同于说“我保证我不再关心这个对象的状态”。std::move
std::move
由于您将使对象基本上不可用,并且您希望确保调用方不再使用该对象,因此使用右值引用在某种程度上强制执行此期望。它迫使调用方向你的函数做出该承诺。未能做出承诺将导致编译器错误(假设没有其他有效的重载)。你是否真的搬离了这个对象并不重要,重要的是原来的所有者已经同意放弃它的所有权。
评论
从某种意义上说,您在这里尝试做的事情并不“危险”,因为鉴于当前的接口,似乎没有任何情况,在这种情况下,此处采用右值引用必然会导致未定义的行为,而采用左值引用则不会。但是恕我直言,整个装置的语义充其量是非常值得怀疑的。调用代码“放弃所有权”但同时“不转让所有权”是什么意思?返回后谁“拥有”流!?究竟在什么方面使流“无法使用”?如果在整个流被“消耗”之前由于某些错误而解析失败怎么办?那么流“不可用”了吗!?没有人可以尝试阅读其余部分吗?在这种情况下,“所有权”是否以某种方式“归还”给调用代码?std::istream
parse()
parse()
流是一个抽象的概念。流抽象的目的是充当一个接口,通过该接口,人们可以使用输入,而不必知道数据的来源、存在或如何访问和管理数据。如果目的是解析来自任意源的输入,那么它不应该关注源的性质。如果它涉及来源的性质,那么它应该请求特定类型的来源。恕我直言,这就是您的界面自相矛盾的地方。目前,采用任意来源。界面说:我接受你给我的任何流,我不在乎它是如何实现的。只要它是流,我就可以使用它。同时,它要求调用方放弃实际实现流的对象。接口要求调用方交出接口本身阻止接口后面的任何实现以任何方式了解、访问或使用的内容。例如,我将如何从?之后谁会关闭文件?如果不能成为解析器。它也不能是我,因为调用解析器迫使我交出对象。同时,我知道解析器甚至永远不知道它必须关闭我交出的文件......parse()
parse()
parse()
std::ifstream
它最终仍然会做正确的事情,因为接口的实现不可能真正完成接口建议它会做的事情,所以我的析构函数将运行并关闭文件。但是,我们这样互相撒谎究竟得到了什么!?你答应过在你永远不会去的时候接管这个物体,我答应过再也不碰那个东西,当我知道我永远必须这样做时......std::ifstream
你认为右值参考参数意味着“获得所有权”的假设是完全不正确的。右值引用只是一种特定的引用,它自带初始化规则和重载解析规则。不多也不少。从形式上讲,它与引用对象的“移动”或“所有权”没有特别的亲和力。
诚然,对移动语义的支持被认为是右值引用的主要目的之一,但您仍然不应该认为这是它们的唯一目的,并且这些功能在某种程度上是不可分割的。就像任何其他语言功能一样,它可能允许大量完善的替代惯用用法。
与您刚才引用的类似示例引用实际上存在于标准库本身中。这是 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
}
它利用了这样一个事实,即右值引用可以绑定到临时对象,并且仍然将它们视为可修改的对象。在这种情况下,右值引用用于与移动语义无关的目的。这是完全正常的。
评论
std::move
std::cin
std::stringstream
std::fstream
std::cin